一.对socket的理解
1.socket本身有“插座“的意思,因此用来描述网络连接的一对一关系。
2.在TCP/IP协议中,“IP地址+TCP/UDP端口号”唯一标识网络通信中的一个进程,“IP地址+端口号”就称为socket。
3.在TCP协议中,建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socketpair 就唯一标志一个连接。
二.网络数据流
网络数据流有大端和小端之分。发送主机通常将发送缓冲区中的数据按内存地址从高到低的顺序 发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存。因此 ,网络数据流的地址应这样规定:先发出的数据都是低地址,后发出的数据是高地址。
TCP/IP协议规定:网络数据流应采用大端字节序,即低地址高字节。
如果发送主机是小端字节序的,则需要做相应的字节序的转换。为使网络程序具有可移植性,使代码在大端和小端计算机上都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
其中h表示host,n表示network,l表示32位长整数,s表示16位短整数。例如:htonl表示将32位的长整数从主机字节序转换为网络字节序。
三.TCP协议通讯流程
服务器:调用socket(),bind(),listen()完成初始化以后,调用accept()阻塞等待,处于监听端口的状态。
客户端:调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。
数据传输的过程:建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从accept()返回后立即调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发送给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read() 返回,发送下一条请求,如此循环下去。
如果客户端没有更多的请求,就调用close()关闭连接,就如同写端关闭的管道一样,服务器的read()返回0,服务器就知道客户端关闭了连接,也调用close()关闭连接。注意:?任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown()则连接处于半关闭状态,仍可接受对方发送来的 数据。
四.TCP协议使用的相关函数
1.socket的创建
(1)函数原型:
(2)参数说明:
domain为协议域,对于IPv4,family指定参数为AF_INET。
type 为指定socket的类型,对于TCP协议,type的参数指定为SOCK_STREAM,表示面向流的传输协议;如果是UDP协议,则type的参数指定为SOCK_DGRAM,表示面向数据报的传输协议。
protocal参数为指定协议,一般指定为0即可。
(3)返回值
2.bind():服务器通过调用bind()绑定一个固定的网络地址和端口号,客户端程序在得知服务器程序的地址和端口号后就可以向服务器发起连接。
(1)函数原型:
(2)参数说明:
sockfd:套接字描述符。
addr:是一个sockaddr结构指针。
addr_len:指定addr的长度。
(3)bind()函数的返回值:
bind()的作用是将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络的文件描述符监听myaddr所描述的地址和端口号。struct socksddr* 是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度。对myaddr参数的初始化如下:
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
首先将整个 结构体清零,然后设置地址类型为AF_INET,网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户建立了连接时才确定到底用哪个IP地址。
注意:我们常用的IP地址是用点分十进制表示的,因此要进行转换。inet_addr()函数用于把一个点分十进制的IP转换成一个长整型数。
函数原型如下:
3.listen函数
(1)函数原型
(2)参数说明
sockfd:套接字描述符。
backlog:最多允许backlog个客户端处于连接等待状态。一般为5——10个。
(3)返回值
4.accept函数
(1)函数原型
(2)函数说明
三方握手完成后,服务器调用accept()接受连接。如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户连接上来。accept()返回时传出客户端的地址和端口号。addrlen参数是一个传入传出型参数,传入的是调用者提供的缓冲区cliaddr的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度。
(3) 返回值
5.客户端使用connect函数
(1)函数原型
(2)函数说明
由于客户端不需要固定的端口号,因此没有必要调用bind(),客户端的端口号由内核自动分配。
服务器也不是必须要调用bind(),但如果服务器不调用bind(),内核会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器时 就会遇到麻烦。
客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。
(3)返回值
五.客户端/服务器程序代码
server端:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<sys/types.h>
5 #include<sys/socket.h>
6 #include<arpa/inet.h>
7 #include<netinet/in.h>
8
9 const int g_backlog=5;
10
11 void usage(const char* _proc)
12 {
13 printf("%s [ip][port]\n",_proc);
14 exit(1);
15 }
16 static int startup(const char* _ip,int _port)
17 {
18 int sock=socket(AF_INET,SOCK_STREAM,0);
19 if(sock<0)
20 {
21 perror("socket");
22 exit(2);
23 }
24 struct sockaddr_in local;
25 local.sin_family=AF_INET;
26 local.sin_port=htons(_port);
27 local.sin_addr.s_addr=inet_addr(_ip);
28
29 if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
30 {
31 perror("bind");
32 exit(3);
33 }
34
35 if(listen(sock,g_backlog)<0)
36 {
37 perror("listen");
38 exit(4);
39 }
40 return sock;
41 }
42
43 void *thread_run(void *sock)
44 {
45 char buf[1024];
46 int accept_sock=(int)sock;
47 while(1)
48 {
49 memset(buf,'\0',sizeof(buf));
50 size_t size=read(accept_sock,buf,sizeof(buf)-1);
51 if(size<0)
52 {
53 perror("read");
54 break;
55 }
56 if(size==0)
57 {
58 printf("client is close...\n");
59 break;
60 }
61 else
62 {
63 printf("client say:%s",buf);
64 }
65 }
66 }
67
68 int main(int argc,char *argv[])
69 {
70 if(argc!=3)
71 {
72 usage(argv[0]);
73 exit(5);
74 }
75 char *ip=argv[1];
76 int port=atoi(argv[2]);
77 int listen_sock=startup(ip,port);
78
79 struct sockaddr_in client;
80 socklen_t len=sizeof(client);
81 while(1)
82 {
83 int new_sock=accept(listen_sock,(struct sockaddr*)&client,&l en);
84 char *client_ip=inet_ntoa(client.sin_addr);
85 int client_port=ntohs(client.sin_port);
86 if(new_sock<0)
87 {
88 perror("accept");
89 continue;
90 }
91 printf("client ip:%s,client_port:%d\n",client_ip,client_port );
92 #ifdef _V1_
93 char buf[1024];
94 while(1)
95 {
96
97 memset(buf,'\0',sizeof(buf));
98 ssize_t size=read(new_sock,buf,sizeof(buf)-1);
99 if(size<0) //read success
100 {
101 perror("read");
102 break;
103 }
104 if(size==0) //client close
105 {
106 printf("client %s is close...\n",client_ip);
107 exit(6);
108 }
109 else
110 {
111 printf("client say:%s",buf);
112 }
113 }
114 #elif _V2_
115 pid_t pid=fork();
116 if(pid<0)
117 {
118 perror("fork");
119 exit(7);
120 }
121 else if(pid==0)//child
122 {
123 char buf[1024];
124 close(listen_sock);
125 while(1)
126 {
127 memset(buf,'\0',sizeof(buf));
128 ssize_t size=read(new_sock,buf,sizeof(buf)-1 );
129 if(size<0)
130 {
131 perror("read");
132 break;
133 }
134 if(size==0)
135 {
136 printf("client %s is close...\n",cli ent_ip);
137 exit(8);
138 }
139 else
140 {
141 printf("client say:%s",buf);
142 }
143
144 }
145 close(new_sock);
146 exit(9);
147 }
148 else
149 {
150 close(new_sock);
151 }
152 #elif _V3_
153 pthread_t tid;
154 if(pthread_create(&tid,NULL,thread_run,(void *)new_sock)!=0)
155 {
156 perror("pthread_create");
157 exit(10);
158 }
159 pthread_detach(tid);
160 #else
161 printf("default\n");
162 #endif
163 }
164 return 0;
165 }
client端:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<unistd.h>
5 #include<sys/socket.h>
6 #include<sys/types.h>
7 #include<netinet/in.h>
8 #include<arpa/inet.h>
9
10 void usage(const char *_proc)
11 {
12 printf("%s [ip][port]\n",_proc);
13 exit(1);
14 }
15
16 int main(int argc,char *argv[])
17 {
18 if(argc!=3)
19 {
20 usage(argv[0]);
21 exit(2);
22 }
23 int server_port=atoi(argv[2]);
24 char *server_ip=argv[1];
25 int sock=socket(AF_INET,SOCK_STREAM,0);
26 if(sock<0)
27 {
28 perror("socket");
29 exit(3);
30 }
31 struct sockaddr_in remote;
32 remote.sin_family=AF_INET;
33 remote.sin_port=htons(server_port);
34 remote.sin_addr.s_addr=inet_addr(server_ip);
35
36 int ret=connect(sock,(struct sockaddr*)&remote,sizeof(remote));
37 if(ret<0)
38 {
39 perror("connect");
40 exit(4);
41 }
42
43 char msg[1024];
44 while(1)
45 {
46 printf("please input:");
47 fgets(msg,sizeof(msg)-1,stdin);
48 fflush(stdout);
49 ssize_t size=write(sock,msg,sizeof(msg)-1);
50 if(size<0)
51 {
52 perror("write");
53 continue;
54 }
55 }
56 return 0;
57 }
(1)_V1_运行结果:
_V1_处理的服务器只能处理一个连接,在连接成功后就进行数据传输,再有其他连接请求的时候就不能监听到,也就不能处理了。这就需要把监听和处理连接的部分分开,_V2_对_V1_程序的改进。
(2)_V2_运行结果:
这就实现了一台服务器多个客户端的传输数据方式。但是这个程序中父进程并没有等待子进程,因此会子进程运行结束后会变成僵尸进程。因此_V3_采用多线程的方式将监听和处理分开,从而解决了子进程会变为僵尸进程的问题。
转载于:https://blog.51cto.com/10548195/1788863