TCP服务器
基于上一篇文章,我们利用socket编写了一个UDP服务器,链接如下:UDP服务器
今天我们来实现一个TCP服务器并且对这个TCP服务器进行优化。
TCP协议的特点
- 有连接的
- 面向字节流的
- 可靠的传输
-
TCP服务器需要用到的接口
TCP服务器和UDP服务器相同,都是利用SOCKET API接口来实现的,那么创建socket,和绑定bind和UDP是一摸一样的,但是因为我们上面讲到的TCP协议是有连接的,所以它不仅仅是创建和绑定就完成了,还需要连接的过程,因此就比UDP协议多出了下列几个接口:
1.listen()
listen这个函数用于把我们创建的套接字设置为监听套接字,用来接收连接请求,第一个参数sockfd是要设置为接听套接字的文件描述符,至于这里的第二个参数backlog,即表示等待队列长度,这个参数是什么意思呢?当我们的连接请求太多的时候,服务器就会把这些来不及服务的连接请求放入这个等待队列当中,因此这个队列长度我们一般不用设置太大,不然会造成客户盲等。
调用成功返回的是0,失败返回-1。此时套接字称之为监听套接字。
2.accpet
当我们创建好监听套接字之后,这个套接字就好像一个吆喝客人的人,一直帮我们处理连接请求,但是它只负责处理连接,并不负责处理具体的服务,而accept()这个函数就是解决这个问题的。它对于每一个连接请求,创建一个对应的socket来服务这个连接,就好像我们进饭馆吃饭的时候,每一桌有一个专门的服务人员服务我们一样。注意第一个参数,一定是我们创建的监听套接字。不难理解,这里的结构体sockaddr肯定就是客户啦。
3.connetc
完成了上面两部,一个服务器的基本框架以及算得上完成了,这时候就需要客户来连接请求了,因此connect就是用于客户端来进行连接的。这里的sockaddr肯定是服务器的地址啦。
4.read和write
在我们编写UDP服务器的时候,我们使用的是recvfrom和sendto来实现数据的交互,但是别忘了我们的socket本质上是一个文件描述符,那么当然可以像读写文件一样的对它进行数据的写入和读出啦。下面实现的TCP服务器就是使用read和write的。
代码实现
服务器端:
1 #include<stdio.h>
2 #include<sys/socket.h>
3 #include<sys/types.h>
4 #include<arpa/inet.h>
5 #include<stdlib.h>
6 #include<string.h>
7 #include<string.h>
8 void useerro(char*argv[])
9 {
10
11 printf("[%s][ ip:][port]\n",argv[0]);
12 exit(1);
13 }
14 int main(int argc,char* argv[] )
15 {
16 if(argc!=3)
17 {
18 useerro(argv);
19 }
20 int sock=socket(AF_INET,SOCK_STREAM,0);
21 if(sock<0)
22 {
23
24 perror("socket");
25 exit(2);
26 }
27
28 struct sockaddr_in listen_socket;
29 struct sockaddr_in server;
30 server.sin_family=AF_INET;
31 server.sin_port=htons(atoi(argv[2]));
32 server.sin_addr.s_addr=inet_addr(argv[1]);
33 if(bind(sock,(struct sockaddr*)&server,sizeof(server))<0)
34 {
35
36 perror("bind");
37 close(sock);
38 exit(3);
39 }
40 if(listen(sock,10)<0)
41 {
42
43 perror("listen");
44 close(sock);
45 exit(4);
46 }
47 printf("bind and listen successful,wait accept\n");
48
49 while(1)
50 {
51
52 socklen_t len=0;
53 int listen_sock=accept(sock,(struct sockaddr*)&listen_socket,&len);
54 if(listen_sock<0)
55 {
56 perror("accept");
57 close(sock);
58 exit(5);
59 }
60 char buf_ip[100];
61 memset(buf_ip,'\0',sizeof(buf_ip));
62 inet_ntop(AF_INET,&listen_socket.sin_addr,buf_ip,sizeof(buf_ip));
63 printf("get connect,ip is:%s,port if:%d\n",buf_ip,ntohs(listen_socket.sin_port));
64 while(1)
65 {
66
67 char buf[1024];
68 memset(buf,'\0',sizeof(buf));
69 read(listen_sock,buf,sizeof(buf));
70 printf("client :#%s\n",buf);
71 printf("server:$");
72
73 memset(buf,'\0',sizeof(buf));
74 fgets(buf,sizeof(buf),stdin);
75 buf[strlen(buf)-1]='\0';
76 write(listen_sock,buf,strlen(buf)+1);
77 printf("please wait ...\n");
78
79 }
80 }
81 close(sock);
82 return 0;
客户端:
1 #include<stdio.h>
2 #include<netinet/in.h>
3 #include<sys/types.h>
4 #include<sys/socket.h>
5 #include<string.h>
6 #include<arpa/inet.h>
7 #include<stdlib.h>
8 #define SERVER_PORT 8080
9 #define SERVER_IP "127.0.0.1"
10 void useerro(char*argv[])
11 {
12
13 printf("[%s][server ip:]\n",argv[0]);//客户端不需要绑定端口,由内核自动分配
14 exit(1);
15 }
16 int main(int argc,char *argv[])
17 {
18 if(argc!=2)
19 {
20
21 useerro(argv);
22 }
23 char buf[1024];
24 memset(buf,'\0',sizeof(buf));
25 int sock=socket(AF_INET,SOCK_STREAM,0);
26 if(sock<0)
27 {
28
29 perror("socket");
30 exit(2);
31
32 }
33 struct sockaddr_in server;
34 bzero(&server,sizeof(server));
35 server.sin_family=AF_INET;
36 server.sin_port=htons(SERVER_PORT);
37 inet_pton(AF_INET,SERVER_IP,&server.sin_addr);
38 int ret=connect(sock,(struct sockaddr*)&server,sizeof(server));
39 if(ret<0)
40 {
41
42 printf("connect failed...\n");
43 exit(3);
44 }
45 printf("connect success\n");
46 while(1)
47 {
48 printf("client :#");
49 fgets(buf,sizeof(buf),stdin);
50 buf[strlen(buf)-1]='\0';
51 write(sock,buf,sizeof(buf));
52 if(strncasecmp(buf,"quit",4)==0)
53 {
54 printf("quit\n");
55 break;
56
57 }
58 printf("please wait:#\n");
59 read(sock,buf,sizeof(buf));
60 printf("server :%s\n",buf);
61 }
62 close(sock);
63 return 0;
64 }
验证结果:
这里我们ip地址使用127.0.0.1 ,本地环回测试。
服务器端:
客户端:
但是我们写的这个服务器是单进程的,它一次只能处理一个连接请求,因此我们可以对它进行优化,实现多进程多线程的服务器,这样就可以处理多个连接请求。
多进程服务器:
1 #include<stdio.h>
2 #include<sys/socket.h>
3 #include<sys/types.h>
4 #include<arpa/inet.h>
5 #include<stdlib.h>
6 #include<string.h>
7 #include<string.h>
8 #include<unistd.h>
9 void useerro(char*argv[])
10 {
11
12 printf("[%s][ ip:][port]\n",argv[0]);
13 exit(1);
14 }
15 void ProcessRequest(int client_sock,struct sockaddr_in*client_addr)
16 {
17
18 char buf_ip[100];
19 memset(buf_ip,'\0',sizeof(buf_ip));
20 inet_ntop(AF_INET,&client_addr->sin_addr,buf_ip,sizeof(buf_ip));
21 printf("get connect,ip is:%s,port is:%d\n",buf_ip,ntohs(client_addr->sin_port));
22 char buf[1024];
23 memset(buf,'\0',sizeof(buf));
24 while(1)
25 {
26 ssize_t read_size=read(client_sock,buf,sizeof(buf));
27 if(read_size<0)
28 {
29
30 perror("read");
31 continue;
32 }
33 else if(read_size==0)
34 {
35
36 printf("client:%s say goodbye\n",inet_ntoa(client_addr->sin_addr));
37 close(client_sock);
38 break;
39 }
40 else
41 {
42 buf[read_size]=0;
43 printf("client[%s] say:#%s\n",inet_ntoa(client_addr->sin_addr),buf);
44 write(client_sock,buf,strlen(buf));
45 }
46
47 }
48 }
49 void createworker(int client_sock,struct sockaddr_in*client_addr)
50 {
51 pid_t pid;
52 pid=fork();
53 if(pid==0)
54 {//child
55 if(fork()==0)
56 {
57 //grandchild
58 ProcessRequest(client_sock,client_addr);
59 }
60 else
61 {
62 exit(0);
63 }
64
65 }
66 else if(pid>0)
67 {//father
68
69 waitpid(pid,NULL,0);
70 close(client_sock);
71 }
72 else
73 {
74
75 perror("fork");
76 return ;
77 }
78
79
80 }
81 int main(int argc,char* argv[] )
82 {
83 if(argc!=3)
84 {
85 useerro(argv);
86 }
87 int sock=socket(AF_INET,SOCK_STREAM,0);
88 if(sock<0)
89 {
90
91 perror("socket");
92 exit(2);
93 }
94
95 struct sockaddr_in server;
96 server.sin_family=AF_INET;
97 server.sin_port=htons(atoi(argv[2]));
98 server.sin_addr.s_addr=inet_addr(argv[1]);
99 if(bind(sock,(struct sockaddr*)&server,sizeof(server))<0)
100 {
101
102 perror("bind");
103 close(sock);
104 exit(3);
105 }
106 if(listen(sock,10)<0)
107 {
108
109 perror("listen");
110 close(sock);
111 exit(4);
112 }
113 printf("bind and listen successful,wait accept\n");
114
115 while(1)
116 {
117
118 struct sockaddr_in client_addr;
119 socklen_t len=0;
120 int client_sock=accept(sock,(struct sockaddr*)&client_addr,&len);
121 if(client_sock<0)
122 {
123 perror("accept");
124 continue;
125 }
126 createworker(client_sock,&client_addr);
127
128
129 }
130 close(sock);
131 return 5;
132 }
这里要说明一下,其中有一个重要的点,就是两次fork的原因。我们的父进程在创建完子进程之后,必须要wait或者waitpid来等待子进程的退出,否则可能造成僵尸进程,但是我们希望的是,当来一个连接请求的时候,就fork出一个进程来处理它,紧接着又去处理别的连接请求,不必关心创建的进程的服务情况,要解决这个问题,有两个方法,一是在学习信号的时候,我们知道子进程退出会给父进程发送一个17号的SIGCHLD信号,当我们不关心子进程的退出情况时,可以把这个信号捕捉为忽略。
第二种方式就是我们现在实现的,通过两次fork,第一次fork完成,父进程等待子进程,子进程再次fork出孙子进程,此时子进程退出,那么父进程等待结束,继续处理连接,而孙子进程由于父进程退出变成了一个孤儿进程,因此需要被1号进程领养,进程结束由系统自动回收资源,不必关心。
但是多进程服务器也有缺点:每一次连接都要创建一个进程,而创建进程就要分配PCB,和虚拟地址空间,简历虚拟地址和物理地址的页表映射,当连接请求多的时候,服务器压力很大,因此我们想到了多线程。
多线程版本:
1 #include<stdio.h>
2 #include<sys/socket.h>
3 #include<sys/types.h>
4 #include<arpa/inet.h>
5 #include<stdlib.h>
6 #include<string.h>
7 #include<string.h>
8 #include<pthread.h>
9 typedef struct Arg
10 {
11 int fd;
12 struct sockaddr_in addr;
13 }Arg;
14 void useerro(char*argv[])
15 {
16
17 printf("[%s][ ip:][port]\n",argv[0]);
18 exit(1);
19 }
20 void ProcessRequest(int client_sock,struct sockaddr_in* client_addr)
21 {
22 char buf[1024]={0};
23 while(1)
24 {
25 ssize_t read_size=read(client_sock,buf,sizeof(buf));
26 if(read_size<0)
27 {
28 perror("read");
29 continue;
30 }
31 else if(read_size==0)
32 {
33 printf("client[%s]:say good bye\n",inet_ntoa(client_addr->sin_addr));
34 close(client_sock);
35 break;
36
37 }
38 buf[read_size]=0;
39 printf("client[%s]say:#%s\n",inet_ntoa(client_addr->sin_addr),buf);
40 write(client_sock,buf,strlen(buf));
41 }
42 return ;
43
44 }
45 void*createworker(void *ptr)
46 {
47 Arg*arg=(Arg*)ptr;
48 ProcessRequest(arg->fd,&arg->addr);
49 free(arg);
50 arg=NULL;
51 return NULL;
52
53 }
54 int main(int argc,char* argv[] )
55 {
56 if(argc!=3)
57 {
58 useerro(argv);
59 }
60 int sock=socket(AF_INET,SOCK_STREAM,0);
61 if(sock<0)
62 {
63
64 perror("socket");
65 exit(2);
66 }
67
68 struct sockaddr_in listen_socket;
69 struct sockaddr_in server;
70 server.sin_family=AF_INET;
71 server.sin_port=htons(atoi(argv[2]));
72 server.sin_addr.s_addr=inet_addr(argv[1]);
73 if(bind(sock,(struct sockaddr*)&server,sizeof(server))<0)
74 {
75
76 perror("bind");
77 close(sock);
78 exit(3);
79 }
80 if(listen(sock,10)<0)
81 {
82
83 perror("listen");
84 close(sock);
85 exit(4);
86 }
87 printf("bind and listen successful,wait accept\n");
88
89 while(1)
90 {
91 struct sockaddr_in client_addr;
92 socklen_t len=0;
93 int client_sock=accept(sock,(struct sockaddr*)&client_addr,&len);
94 if(client_sock<0)
95 {
96 perror("accept");
97 continue;
98 }
99 pthread_t t1;
100 Arg* arg=(Arg*)malloc(sizeof(Arg));
101 arg->fd=client_sock;
102 arg->addr=client_addr;
103 pthread_create(&t1,NULL,createworker,(void*)arg);
104 pthread_detach(t1);
105 }
106 close(sock);
107 return 0;
108 }
同样为了解决线程需要等待的问题,我们把线程分离掉,多线程也有缺点:因为线程之间大部分资源是共享的,因此必须保证同步和互斥,线程安全必须要考虑。