简单的socket编程 复习下C++的socket编程
step 1:调用socket()函数创建套接字 Linux系统下的socket API。
1 2 3 #include <sys/socket.h> int socket (int domain,int type,int protocol)
int domin
:即协议域,又称为协议族。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。通常情况下我们使用AF_INET。
int type
:是套接口类型,主要SOCK_STREAM(建立TCP连接)、SOCK_DGRAM(建立UDP)、SOCK_RAW;SOCK_STREAM(建立TCP连接):提供面向连接的可靠的数据传输服务。数据被看作是字节流,无长度限制。例如FTP协议就采用这这种。SOCK_DGRAM(建立UDP):提供无连接的数据传输服务,不保证可靠性。SOCK_RAW:该接口允许对较低层次协议,如IP,ICMP直接访问。
int protocol
:指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。通常情况系设为0。
注意 :并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
step 2:调用bind()函数初始化套接字的IP地址和端口号 1 2 3 #include <sys/socket.h> int bind (int sockfd,struct sockaddr *myaddr,socklen_t addrlen)
调用bind函数为创建好的socket套接字分配网络IP地址和端口,第一个参数sockfd就是socket函数返回的文件描述符(用于找到是哪个socket)。
第二个参数就相当重要了,首先需要借助\中的sockaddr_in来填写这个sockaddr
1 2 3 4 5 6 7 8 9 10 11 struct sockaddr_in serv_addr ;memset (&serv_addr,0 ,sizeof serv_addr);serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=inet_addr ("127.0.0.1" ); serv_addr.sin_port=htons (atoi (argv[1 ])); if (bind (serv_sock,(struct sockaddr*)&serv_addr,sizeof serv_addr)<0 ) { error_handle ("bind" ,"bind() error." ); }
为什么serv_addr可以直接强制转换成sockaddr呢?先看下源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct sockaddr { __SOCKADDR_COMMON (sa_); char sa_data[14 ]; }; struct sockaddr_in { __SOCKADDR_COMMON (sin_); in_port_t sin_port; struct in_addr sin_addr ; unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t ) - sizeof (struct in_addr)]; };
可以看到其实sockaddr无非就是将sockaddr_in这个结构体变成了char数组(方便传输吧),两者的大小都是一样的,所以可以直接转换。
htons函数的拓展(摘录):
不同的计算机里对它们的多字节整数(例如:一个大于char的整数)使用不同的字节顺序。这就意 味着,如果你从intel的盒子中send()一个两个字节的短整数到一个Mac中(在它们成为Intel之前),一个计算机认为是数字1,而另外一个计 算机会认为它是数字256,反之亦然。
解决这个问题的办法是:所有的人撇开它们的不同之处,同意摩托罗拉和IBM的顺序是正确的,而Intel使用的是怪异的方式,所以我们在将他们发送 出去之前把所有的字节以“正序”方式排列。既然Intel是一个“反序”的机器,所以调用我们需要的,以“网络字节顺序”排列的字节是很正确的。所以这些 函数把你的本机字节顺序转化为网络字节顺序然后再转化回来。
(这就意味着,在Intel上面,这些函数把所有的字节调换过来,而在PowerPC上面,它们什么都没有做,因为在那上面字节本身是以网络字节顺序排列的。但是你依然要在你的程序当中使用它们,因为别的人有可能将它们应用到Intel机器当中,使之依然正常的运行。)
注意到这些类型包含32-位(4个字节,可能是整数)和16-位(两个字节和short很像)数据。64-位的机器可以使用htonll()作为64-位整数,但是我们没有见到它。你必须自己写。
不管怎样,使用这些函数决定于你要从主机字节顺序(你的电脑上的)还是网络字节顺序转化。如果是”host”,函数的第一个字母为”h”,否 则”network”就为”n”。函数的中间字母总是”to”,因为你要从一个转化到另一个,倒数第二个字母说明你要转化成什么。最后一个字母是数据的大 小,”s”表示short,”l”表示long。于是:
1 2 3 4 htons () htonl () ntohs () ntohl ()
返回值:
每个函数返回转化后的值
step 3:调用listen()函数将socket套接字转为可接收请求状态 1 2 3 #include <sys/socket.h> int listen (int sockfd,int backlog) ;
前面完成对socket套接字的网络IP地址和端口绑定后,通过调用listen函数使socket套接字进入等待接听的状态,第二个参数设置同一时间运行等待接听的对方机器数量(对方建立连接前会处于等待队列中)
step 4:调用accept()函数接收连接请求,并返回能够正常进行数据通信的文件描述符 1 2 3 #include <sys/socket.h> int accept (int sockfd,struct sockaddr *addr,socklen_t *addrlen) ;
进入等待接听后,调用accept()函数,正常情况下回进入阻塞状态,等待连接到达这个socket绑定的IP端口上,一旦接收到对方请求就会返回一个建立正常数据通信的文件描述符供双方使用,并且会将对方的IP地址及端口信息使用指针传参的方式传递回来。
Example 服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <iostream> #include <string> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <arpa/inet.h> #include <sys/socket.h> void error_handle (std::string opt,std::string message) { perror (opt.c_str ()); std::cout << message << std::endl; exit (1 ); } int main (int argc,char * argv[]) { int serv_sock; int client_sock; struct sockaddr_in serv_addr ; struct sockaddr_in client_addr ; socklen_t client_addr_size; char message[]="hello world" ; if (argc<2 ) { std::cout << "Usage : " << argv[0 ] << " <port>" << std::endl; exit (1 ); } serv_sock=socket (PF_INET,SOCK_STREAM,0 ); if (serv_sock<0 ) { error_handle ("socket" ,"socket() error." ); } memset (&serv_addr,0 ,sizeof serv_addr); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=inet_addr ("127.0.0.1" ); serv_addr.sin_port=htons (atoi (argv[1 ])); if (bind (serv_sock,(struct sockaddr*)&serv_addr,sizeof serv_addr)<0 ) { error_handle ("bind" ,"bind() error." ); } if (listen (serv_sock,5 )<0 ) { error_handle ("listen" ,"listen() error." ); } std::cout << "Waiting Client......" << std::endl; client_sock=accept (serv_sock,(struct sockaddr*)&client_addr,&client_addr_size); if (client_sock<0 ){ error_handle ("accept" ,"accept() error" ); } std::cout << "Client IP : " << inet_ntoa (client_addr.sin_addr) << ", port : " << ntohs (client_addr.sin_port) << std::endl; write (client_sock,message,sizeof message); close (client_sock); close (serv_sock); return 0 ; }
客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <iostream> #include <string> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <arpa/inet.h> #include <sys/socket.h> void error_handle (std::string opt,std::string message) { perror (opt.c_str ()); std::cout << message << std::endl; exit (1 ); } int main (int argc,char * argv[]) { int sock; struct sockaddr_in serv_addr ; char message[64 ]; int str_len; if (argc<3 ){ std::cout << "Usage : " << argv[0 ] << " <IP> <port>" << std::endl; exit (1 ); } sock=socket (PF_INET,SOCK_STREAM,0 ); if (sock<0 ){ error_handle ("socket" ,"socket() error." ); } memset (&serv_addr,0 ,sizeof serv_addr); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=inet_addr (argv[1 ]); serv_addr.sin_port=htons (atoi (argv[2 ])); if (connect (sock,(struct sockaddr*)&serv_addr,sizeof serv_addr)<0 ){ error_handle ("connect" ,"connect() error." ); } str_len=read (sock,message,sizeof (message)-1 ); if (str_len<0 ){ error_handle ("read" ,"read() error." ); } std::cout << "Recv Message : " << message << std::endl; close (sock); return 0 ; }