之前我们学习了系统的进程间通信(IPC)机制:管道,消息队列,共享内存,信号量等,这些机制允许在同一台计算机中的两个进程间通信。而今天的网络进程间通信(网络IPC)实现了不同的计算机的两个进程间的通信。网络IPC实现的机制是网络套接字。
套接字是通信端点的抽象,通常用IP地址和端口号来描述。
IP地址表示接入网络的主机的标识,端口号表示该主机上唯一的进程。一对IP地址和端口号标识了计算机网络中的两个唯一的进程。
1. 创建套接字
正如文件描述符一样,应用程序用套接字描述符访问套接字。套接字描述符在系统中被当作是一种文件描述符。
#include <sys/socket.h>
int socket ( int domain,int type, int protocol );
参数domain(域)确定通信的特性,各个域都以常数AF_开头:
参数type确定套接字的类型,进一步确定通信特征
参数protocol通常是0.表示为给定的域和套接字类型选择默认协议。例如:在AF_INET域中的SOCK_STREAM的默认协议为TCP(传输控制协议),在AF_INET域中的SOCK_DGREAM的默认协议为UDP(用户数据报协议)。
注:数据报提供了无连接的服务,字节流(SOCK_TREAM)要求在交换数据之前,在本地套接字和通信的进程的套接字之间建立一个逻辑连接。
注意:调用socket创建套接字和打开文件一样,在创建完毕,需要调用close函数关闭对文件或套接字的访问。
补充:套接字通信是双向的,可以采用shutdown来禁止一个套接字的IO。
#include<sys/socket.h>
int shutdown( int sockfd,int how);
参数how 是SHUT_RD时,关闭读端,是SHUT_WR时,关闭写端,是SHUT_RDWR时,关闭读写两端。
2. 网络字节序
与同一台计算机的进程通信时,不需要考虑字节序的问题,但是在网络中,我们需要考虑传输过程中的数据问题,就需要考虑字节序列。
字节序是由处理器架构决定的,一般情况下,我们不能改变。所谓的字节序是指计算机存储数据是以大端存储还是小端存储。
大端存储:数据的低字节位放置在内存的高地址处,数据的高位放置在内存的低地址处;
小端存储:数据的低位放置在内存的低地址处,数据的高位放置在内存的高地址处。
在网络传输中,我们规定以下几点:
(1)通常将缓冲区的数据按内存地址从低到高的顺序发出;
(2)接收主机将接收缓冲区的数据按内存地址从低到高的顺序取出;
(3)TCP/IP参考模型规定,网络数据流采用大端字节序;
为了使数据能被正确的传送和接收,系统提高了以下函数做网络字节序和主机字节序的转化:
注:这些函数很好记,h表示“主机”字节序,n表示“网络"字节序,s表示”短“整型,l表示”长“整型。
3. 地址格式
一个地址标识一个特定通信域的套接字端点,地址格式与通信域相关。为使不同的格式地址能够传入到套接字函数中,地址会被强制转化为通用的地址结构sockaddr:
struct sockaddr{
sa_family_t sa_family;
char sa_data[];}
在IPv4因特网域中(AF_INET),套接字地址结构用sockaddr_in表示:
struct sockaddr_in{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
}
struct sin_addr{
in_addr_t s_addr;
}
4. 将套接字与地址关联
对于客户端的套接字,可以让系统选一个默认的地址。然而,对于服务器端,需要给服务器套接字一个众所周知的地址,并且客户端应有一种方法知道连接服务器的地址,最简单的方法就是服务器保留一个地址并且注册在/etc/services或者某个名字服务中。
使用bind函数来关联地址和套接字:
#include <sys/socket.h>
int bind( int sock,const struct sockaddr* addr, socklen_t len);
返回值:若成功,返回0;若出错,返回-1;
注:对于使用的地址有以下限制:
(1)在进程正在运行的计算机上,指定的地址必须有效,不能指定其他机器的地址。
(2)地址必须和创建套接字的地址族格式匹配。
(3)地址中的端口号不能小于1024.
(4)对于因特网域,如果指定IP地址为INADDR_ANY,套接字端可以被绑定在任意一系统网络接口上。
5. 地址转换函数
在IPv4中,通常以点分十进制来表示IP地址,以下函数表示可以在字符串和整型之间的转换。
注:其中前三个函数表示字符串转整型函数;后三个函数表示整型转字符串(点分十进制)函数。
6. 在介绍完一些函数的基本功能和套接字的基本概念后,我们来实现UDP:
UDP服务器端
创建套接字并绑定
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/socket.h>
4 #include <arpa/inet.h>
5 #include <netinet/in.h>
6 #include <stdlib.h>
7 #include <string.h>
8
9 int my_socket(char* IP,int port)
10 {
11 int sock=socket(AF_INET,SOCK_DGRAM,0);
12 if(sock<0){
13 printf("perror socket!\n");
14 exit(1);
15 }
16 struct sockaddr_in local;
17 local.sin_family=AF_INET;
18 local.sin_port=htons(port);
19 local.sin_addr.s_addr=inet_addr(IP);
20
21 int binding=bind(sock,(struct sockaddr*)&local,sizeof(local));
22 if(binding<0)
23 {
24 printf("perror bind\n");
25 exit(2);
26 }
27 return sock;
28 }
收发数据
29 void revandsenddata(int sock)
30 {
31 char buffer[521];
32 struct sockaddr_in client;
33 while(1)
34 {
35 socklen_t len =sizeof(client);
36 ssize_t s=recvfrom(sock,buffer,sizeof(buffer)-1,0,\
37 (struct sockaddr*)&client,&len);
38 if(s>0)
39 {
40 buffer[s]=0;
41 printf("[%s:%d]:%s\n",inet_ntoa(client.sin_addr),\
42 ntohs(client.sin_port),buffer);
43 sendto(sock,buffer,strlen(buffer),0,\
44 (struct sockaddr*)&client,sizeof(client));
45
46 }
47 }
48 }
50 void help()
51 {
52 printf("please enter IP and port\n");
53 }
54 int main(int argc,char* argv[])
55 {
56 printf("serve:\n");
57 if(argc!=3)
58 {
59 help();
60 exit(3);
61 }
62 char *IP = argv[1];
63 char *port=argv[2];
64
65 int sock=my_socket(IP,atoi(port));
66 printf("%d\n" ,sock);
67 revandsenddata(sock); close(sock);
68 return 0;
69 }
UDP客户端
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <sys/types.h>
5 #include <netinet/in.h>
6 #include <arpa/inet.h>
7 int mysockaddr(char* IP)
8 {
9 int sock=socket(AF_INET,SOCK_DGRAM,0);
10 if(sock<0)
11 {
12 printf("perror sockaddr!\n");
13 exit(1);
14 }
15
16 struct sockaddr_in serve;
17 serve.sin_family=AF_INET;
18 serve.sin_port=htons(8080);
19 serve.sin_addr.s_addr=inet_addr(IP);
20
21 char buffer[521];
22 struct sockaddr_in peer;
23 while(1)
24 {
25 socklen_t len=sizeof(peer);
26 printf("please enter:\n");
27 fflush(stdout);
28
29 ssize_t s=read(0,buffer,sizeof(buffer)-1);
30
31 if(s>0)
32 {
33 buffer[s-1]=0;
34 sendto(sock,buffer,strlen(buffer),0,\
35 (struct sockaddr*)&serve,sizeof(serve));
36 ssize_t ss=recvfrom(sock,buffer,sizeof(buffer)-1,0,\
37 (struct sockaddr*)&peer,&len);
38 if(ss>0)
39 {
40 buffer[s-1]=0;
41 printf("serve echo %s\n",buffer);
42 }
43 }
44 }
45 }
46 void help()
47 {
48 printf("please enter IP\n");
49 }
50
51 int main(int argc,char* argv[])
52 {
53 printf("client:\n");
54 if(argc!=2)
55 {
56 help();
57 exit(2);
58 }
59
60 int sock=mysockaddr(argv[1]);
close(sock);
61 return 0;
62 }
7. 实现TCP
TCP(传输控制协议)是一种有连接,可靠的,面向连接,面向字节流的传输协议。
TCP的实现与UDP的实现类似,不过因为它是有连接的,可靠的,所以我们需要在服务器端监听和接收连接,在客户端需建立连接。
listen函数实现监听
参数sockfd表示创建socket后返回的套接字描述符;
重点说一下backlog,backlog表示等待队列长度,指示系统进程所要入队的未完成的连接的请求的数量,具体的值,取决于系统的实现。一旦队列满,系统就会拒绝其他的请求。
实际上,backlog 的值依赖于服务器负载和处理量。一方面,为了让CPU充分发挥它的作用,backlog是必须要有的,另一方面,服务器和CPU的能力有限,即backlog是不能太大的,在TCP中,默认值为128.
accept函数获得连接并建立连接
一旦服务器调用了listen函数,所用的套接字就能接收连接请求。
#include <stdio.h>
int accept (int sockfd,struct sockaddr* addr,socklen_t * len);
返回值:若成功,返回0,失败返回-1;
函数accept的返回值是套接字描述符,该描述符用来和客户端建立connect,然后通过返回的套接字描述符向客户端提供服务。
这个新的套接字描述符和原套接字描述符的地址族相同,但是实现的功能不同,新的主要提供服务,原的主要响应其他连接请求。
connect函数客户端建立连接
如果要处理面向连接的网络服务(SOCK_STREAM和SOCK_SEQPACKET),就需要在客户端调用connect函数和服务器端建立连接。
#include <sys/socket.h>
int connet(int sockfd,const struct sockaddr* addr,socklen_t len);
8. 实现TCP
TCP服务器端
(1).创建socket
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <netinet/in.h>
5 #include <errno.h>
6 #include <unistd.h>
7 #include <sys/types.h>
8 #include <sys/socket.h>
9
10 #define _BACKLOG 10
11
12 int mysocket(char* IP,int port)
13 {
14 int listen_sock=socket(AF_INET,SOCK_STREAM,0);
15 if(listen_sock<0)
16 {
17 printf("perror socket\n");
18 exit(2);
19 }
20 struct sockaddr_in local;
21 local.sin_family=AF_INET;
22 local.sin_port=htons(port);
23 local.sin_addr.s_addr=inet_addr(IP);
24 int binding=bind(listen_sock,(struct sockaddr*)&local,\
25 sizeof(struct sockaddr_in));
26 if(binding<0)
27 {
28 printf("perror bind\n");
29 exit(3);
30 }
31
32 if(listen(listen_sock,_BACKLOG)<0)
33 {
34 printf("perror listen\n");
35 close(listen_sock);
36 exit(4);
37 }
38
39 return listen_sock;
40 }
(2)服务函数
41 void service(int new_sock)
42 {
43 char buf[1024];
44 memset(buf,0,sizeof(buf));
45 while(1)
46 {
47 ssize_t s=read(new_sock,buf,sizeof(buf));
48 if(s<0)
49 {
50 printf("perror read\n");
51 exit(5);
52 }
53 else if(s==0)
54 {
55 close(new_sock);
56 break;
57 }
58 buf[s]=0;
59 printf("Client: %s\n",buf);
60 write(new_sock,buf,strlen(buf));
61 }
62 }
(3)连接函数
68 int main(int argc,char*argv[])
69 {
70 if(argc!=3)
71 {
72 printf("Usage:[IP],[port]!\n");
73 exit(1);
74 }
75
76 char* IP=argv[1];
77 int port=atoi(argv[2]);
78 int listen_sock=mysocket(IP,port);
79 printf("server:%d\n",listen_sock);
80
81 struct sockaddr_in peer;
82 socklen_t len=sizeof(peer);
83 for(;;)
84 {
85 int new_sock=accept(listen_sock,(struct sockaddr*)&peer,&len);
86 if(new_sock<0)
87 {
88 printf("perror accept\n");
89 continue;
91 char bufip[32];
92 bufip[0]=0;
93 inet_ntop(AF_INET,&peer.sin_addr,bufip,sizeof(bufip));
94 printf("get connect,ip is:%s port is:%d\n",\
95 bufip,ntohs(peer.sin_port));
96 service(new_sock);
97 }
98
99 close(listen_sock);
100
101 return 0;
102 }
TCP客户端
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/types.h>
4 #include <string.h>
5 #include <errno.h>
6 #include <netinet/in.h>
7 #include <arpa/inet.h>
8 int main(int argc,char*argv[])
9 {
10 if(argc!=2)
11 {
12 printf("usage:[IP]\n");
13 return 1;
14 }
15 char *IP=argv[1];
16 int sock=socket(AF_INET,SOCK_STREAM,0);
17 if(sock<0)
18 {
19 printf("perror sock!\n");
20 return 2;
21 }
22
23 struct sockaddr_in local;
24 local.sin_family=AF_INET;
25 local.sin_port=htons(8080);
26 local.sin_addr.s_addr=inet_addr(IP);
27 // inet_pton(AF_INET,IP,&local.sin_addr);
28 int ret=connect(sock,(const struct sockaddr*)&local,sizeof(local));
29
30 if(ret<0){
31
32 printf("perror connect!\n");
33 return 3;
34 }
35
36 printf("connect success!\n");
37
38 char buf[1024];
39 while(1)
40 {
41 memset(buf,0,sizeof(buf));
42 printf("client:please enter!\n");
43 fflush(stdout);
44 ssize_t s=read(0,buf,sizeof(buf));
45 buf[s-1]=0;
46 if(strcmp(buf,"quit")==0)
47 {
48 printf("client quit!\n");
49 break;
50 }
51 write(sock,buf,sizeof(buf));
52 s=read(sock,buf,sizeof(buf));
53 if(s>0)
54 {
55 buf[s]=0;
56 printf("server:%s\n",buf);
57 }
58
59 }
60 close(sock);
61 return 0;
62 }