C语言——网络与套接字
互联网中大部分的底层网络代码都是用C语言写的。网络程序通常由两部分程序组成:服务器和客户端。
服务器将同时与多个客户端通信。客户端与服务器之间将展开一段结构化对话,叫做协议。
互联网使用了各种协议,一部分是低层协议,另一部分是高层协议。
低层协议有IP,它用来控制二进制的0和1在互联网中的发送方式。
高层协议有HTTP,它用来控制浏览器与网络服务器的对话。
协议通常有一套严格的规则。客户端和服务器都遵守了这些规则就没有事,但只要它们有一方违反了规则,对话就会终止。
为了与外界沟通,C程序用数据流读写字节。我们已熟知标准输入、标准输出、标准错误等进程的三大默认数据流。现在,我们要介绍与网络通信有关的新数据流——套接字。
使用套接字与客户端程序通信前,服务器需要经过四个阶段(BLAB): 绑定(Bind),监听(Listen),接受(Accept), 开始(Begin)。
1.绑定端口
计算机可能同时运行多个服务器程序,为了防止不同对话发生混淆,每项服务必须使用不同的端口。服务器在启动时,需要告诉操作系统将要使用哪个端口,这个过程叫端口绑定。为了绑定它, 需要两样东西:套接字描述符和套接字名。套接字名是一个结构体。步骤:先打开套接字,然后再进行端口绑定。
//绑定:套接字描述符 套接字名 //套接字描述符(互联网套接字) int listener_d = socket(PF_INET,SOCK_STREAM,0); if(listener_d == -1){ fprintf(stderr,"无法打开套接字"); } //套接字名 struct sockaddr_in name; name.sin_family = PF_INET; name.sin_port = (in_port_t)htons(30000); name.sin_addr.s_addr = htonl(INADDR_ANY); int c = bind(listener_d,(struct sockaddr *)&name,sizeof(name)); if(c == -1){ fprintf(stderr,"无法绑定端口"); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
2.监听
可以用listen()函数告诉操作系统客户端等待连接的队列的最大长度。调用listen()把队列长度设为10,也就是说最多可以有10个客户端同时尝试连接服务器,它们不会立即得到响应,但可以排队等待,而第11个客户端则会被告知服务器太忙:
//监听 if(listen(listener_d,10) == -1){ fprintf(stderr,"无法监听"); }
- 1
- 2
- 3
- 4
3.接受连接
一旦绑定完端口,设置完监听队列,唯一可以做的就是等待。服务器一生都在等待客户端来连接它。accept()系统调用会一直等待,直到有客户端连接服务器,它会返回第二个套接字描述符,然后就可以用它与客户端通信了:
struct sockaddr_storage client_addr; unsigned int address_size = sizeof(client_addr); int connect_d = accept(listener_d,(struct sockaddr *)&client_addr,&address_size); if(connect_d == -1){ fprintf(stderr,"无法打副套接字"); }
- 1
- 2
- 3
- 4
- 5
- 6
4.开始
服务器就可以用新的套接字描述符connect_d开始与客户端通信了。套接字不是传统意义上的数据流,因此它不能用fprintf()、fscanf()来通信。套接字是双向的,即可以作为输入也可以作为输出,用send()函数向套接字数据流写数据,用recv()函数读取套接字数据流中的数据。。
//开始 char *msg = "Be honest,you look so beautiful!"; //connect_d:套接字描述符,msg:消息,strlen(msg):消息长度,0:高级选项,填0就可以 if(send(connect_d,msg,strlen(msg),0) == -1){ fprintf(stderr,"发送失败"); } close(connect_d);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
好习惯:一定要检查系统调用的返回值。因为网络错误随处可见,服务器必须处理它。
完整的实例test.c:
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h> int main(){ //准备的对话 char *advice[]= { "Take smaller bites\r\n", "Go for the tight jeans\r\n", "What size do you want?\r\n", "Be honest,you look so beautiful!\r\n", "Be careful the tag!\r\n"}; //绑定:套接字描述符 套接字名 //套接字描述符(互联网套接字) int listener_d = socket(PF_INET,SOCK_STREAM,0); if(listener_d == -1){ fprintf(stderr,"无法打开套接字\n"); exit(1); } struct sockaddr_in name; name.sin_family = PF_INET; name.sin_port = (in_port_t)htons(30000); name.sin_addr.s_addr = htonl(INADDR_ANY); int c = bind(listener_d,(struct sockaddr *)&name,sizeof(name)); if(c == -1){ fprintf(stderr,"无法绑定端口\n"); exit(1); } //监听 if(listen(listener_d,10) == -1){ fprintf(stderr,"无法监听\n"); exit(1); } puts("Waiting for connection"); //接受 while(1){ //循环接受客端连接,一连接上,就用新的套接字描述符通信。 struct sockaddr_storage client_addr; unsigned int address_size = sizeof(client_addr); int connect_d = accept(listener_d,(struct sockaddr *)&client_addr,&address_size); if(connect_d == -1){ fprintf(stderr,"无法打副套接字"); } //开始 char *msg = advice[rand() % 5]; if(send(connect_d,msg,strlen(msg),0) == -1){ fprintf(stderr,"发送失败"); } close(connect_d); } 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
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
编译运行:
~/Desktop/MyC$ gcc test.c -o test
~/Desktop/MyC$ ./test
Waiting for connection
|
- 1
- 2
- 3
- 4
上面这个服务器已启动成功,正在等待客户端来连接。
打开一个客户端连接到服务器:
~$ telnet 127.0.0.1 30000
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Be honest,you look so beautiful!
Connection closed by foreign host.
- 1
- 2
- 3
- 4
- 5
- 6
看!客户端收到服务端的响应了,同时服务端也在继续等待其他客户端的连接。
紧接着,我们用ctrl-C杀掉服务器程序,再重新运行:
~/Desktop/MyC$ ./test
Waiting for connection
^C
~/Desktop/MyC$ ./test
无法绑定端口
~/Desktop/MyC$
- 1
- 2
- 3
- 4
- 5
- 6
为什么会这样呢?因为当服务器已经响应某个客户端时,关闭服务器,然后立即重启,bind系统调用会失败。当你在某个端口绑定了套接字,在接下来的30秒内,操作系统不允许任何程序再绑定它,包括上一次绑定这个端口的程序。
怎么办?其实,只要用setsockopt()函数在绑定端口前设置套接字选项就可以解决这个问题,设置如下:
int reuse = 1;
if(setsockopt(listener_d,SOL_SOCKET,SO_REUSEADDR,(char *)&reuse,sizeof(int)) == -1){ fprintf(stderr,"无法设置套接字的”重新使用端口“选项");
}
- 1
- 2
- 3
- 4
设置了这个选项后,套接字就能重新使用已经绑定过的端口,并且可以关闭服务器后马上重启,不用再等30秒了。
最后给出完整的代码:
test.c
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
int main(){ //准备的对话 char *advice[]= { "Take smaller bites\r\n", "Go for the tight jeans\r\n", "What size do you want?\r\n", "Be honest,you look so beautiful!\r\n", "Be careful the tag!\r\n"}; //绑定:套接字描述符 套接字名 //套接字描述符(互联网套接字) int listener_d = socket(PF_INET,SOCK_STREAM,0); if(listener_d == -1){ fprintf(stderr,"无法打开套接字\n"); exit(1); } int reuse = 1; if(setsockopt(listener_d,SOL_SOCKET,SO_REUSEADDR,(char *)&reuse,sizeof(int)) == -1){ fprintf(stderr,"无法设置套接字的”重新使用端口“选项"); } struct sockaddr_in name; name.sin_family = PF_INET; name.sin_port = (in_port_t)htons(30000); name.sin_addr.s_addr = htonl(INADDR_ANY); int c = bind(listener_d,(struct sockaddr *)&name,sizeof(name)); if(c == -1){ fprintf(stderr,"无法绑定端口\n"); exit(1); } //监听 if(listen(listener_d,10) == -1){ fprintf(stderr,"无法监听\n"); exit(1); } puts("Waiting for connection"); //接受 while(1){ struct sockaddr_storage client_addr; unsigned int address_size = sizeof(client_addr); //创建副套接字 int connect_d = accept(listener_d,(struct sockaddr *)&client_addr,&address_size); if(connect_d == -1){ fprintf(stderr,"无法打副套接字"); } //开始 char *msg = advice[rand() % 5]; if(send(connect_d,msg,strlen(msg),0) == -1){ fprintf(stderr,"发送失败"); } close(connect_d); } 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
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
上面的服务器进程会通过副套接字向发客户端发送信息。那么服务器如何接收客户端发送的信息呢?前面提到过:套接字用send()写数据,recv()读数据,所以当然是用recv()函数接收消息。
<读了几个字节> = recv(<套接字描述符>,<缓冲区>,<要读取几个字节>,0);
如果用户在客户端输入一行文本“hello world”,然后按下回车,recv() 就会把文本保存在一个字符数组中,它的内容是:hello world\r\n
注意:
1.字符串不是以\0结尾的,而是以\r\n结尾的。
2.recv()将返回字符个数,如果发生错误就返回-1,如果客户端关闭了与服务器的连接,就返回0。
3.recv()调用不一定能一次接收到所有字符。
char s[255]; int c = recv(socket,s,sizeof(s),0); while((c>0) && (s[c-1] != '\n')){//循环读取字符,直到没有字符可以读或读>到了\n s += c;slen -= c; c = recv(socket,s,slen,0); } if(c <0){ return c;//防止错误 }else if(c == 0){ buf[0] = '\0';//什么也没有读到,返回一个空字符串 }else{ s[c-1] = '\0';//用\0替换\r return len -slen; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
自定义协议
我们自定义一种高层协议——IAHP(Internet Apple Hello Protocol)。协议就是客户端与服务端展开一段结构化对话。而我们的代码就是要保证这种结构化的对话的进行。结构化对话对形式、内容都做了要求。使用协议的各方都必须遵守,否则通信将不成功。
IAHP协议的客户端与服务器之间将像以下这样交换信息:
服务器 | 客户端 |
---|---|
Hi ! Hi ! | |
Who are you? | |
Apple | |
Apple who? | |
Ha Ha Stupid |
我们将上面表述如下:
这时客户端连接到服务器。
服务器说: Hi ! Hi!
客户端必须说:Who are you?
然后服务器答:Apple
客户端调皮地问:Apple who?
服务器生气地说:Ha Ha Stupid
如果服务器说: Hi ! Hi!,客户端不是回答“Who are you?”而是回答成“Who is that”,那么很抱歉,客户端违反了IAHP协议结构化对话的要求,所以程序会被终止。
那么我们如何在服务器程序里保证这种协议的实现呢?程序流程图如下:
流程图 |
---|
1.从客户端取得连接 |
说:”Hi ! Hi!“ |
检查客户端是否回答了:“Who are you?” |
说:“Apple” |
检查客户端是否回答了:“Apple who?" |
说:“Ha Ha Stupid” |
代码实现如下:
int connect_d = accept(listener_d,(struct sockaddr *)&client_addr,&address_size);
if(connect_d == -1){ error("无法打开副套接字");
}
if(send_message_to_client(connect_d,"Internet Apple Hello Protocol Server \r\n Version 1.0\r\n Hi ! Hi !\r\n>") != -1){//向客户端发送数据 read_in(connect_d,buf,sizeof(buf));//从客户端读取数据 if(strncasecmp("Who are you?",buf,12)){ send_message_to_client(connect_d,"You showld say 'Who are you?'!"); }else{ if(send_message_to_client(connect_d,"Apple\r\n>") != -1){ read_in(connect_d,buf,sizeof(buf)); if(strncasecmp("Apple who?",buf,10)){ send_message_to_client(connect_d,"You should say 'Apple who?'!\r\n"); }else{ send_message_to_client(connect_d,"Ha Ha Stupid\r\n"); } } }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
至此,我们已看到一个协议的大概实现过程了。运行一下完整实例:
test.c
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <signal.h>
void error(char *msg);
int read_in(int socket,char *buf,int len);
//创建互联网套接字,listener_d是套接字描述符
int open_listener_socket();
//绑定端口
void bind_to_port(int socket,int port);
//向客户端发送消息
int send_message_to_client(int socket,char *s);
//自定义SIGINT信号处理器函数
void handle_shutdown(int sig);
//捕捉SIGINT信号
int catch_signal(int sig,void (*handler)(int));
int listener_d; //套接字描述符
int main(){
//捕捉SIGINT信号,然后调用自己定义的处理器函数
if(catch_signal(SIGINT,handle_shutdown) == -1){//如果有人按了ctrl-C就调用handle_shutdown()函数
error("无法设置中断处理器");
}
//绑定:套接字描述符 套接字名
listener_d = open_listener_socket();
bind_to_port(listener_d,30000); //监听
if(listen(listener_d,10) == -1){//设置队列长度为10
error("无法监听");
}
struct sockaddr_storage client_addr;
unsigned int address_size = sizeof(client_addr);
puts("Waiting for connection");
char buf[255];
//接受
while(1){
//创建副套接字
int connect_d = accept(listener_d,(struct sockaddr *)&client_addr,&address_size);
if(connect_d == -1){
error("无法打开副套接字");
}
if(send_message_to_client(connect_d,"Internet Apple Hello Protocol Server \r\n Version 1.0\r\n Hi ! Hi !\r\n>") != -1){//向客户端发送数据
read_in(connect_d,buf,sizeof(buf));//从客户端读取数据 if(strncasecmp("Who are you?",buf,12)){ send_message_to_client(connect_d,"You showld say 'Who are you?'!");
}else{ if(send_message_to_client(connect_d,"Apple\r\n>") != -1){ read_in(connect_d,buf,sizeof(buf)); if(strncasecmp("Apple who?",buf,10)){ send_message_to_client(connect_d,"You should say 'Apple who?'!\r\n"); }else{ send_message_to_client(connect_d,"Ha Ha Stupid\r\n"); } }
}
}
//开始
close(connect_d);//关闭套接字
}
return 0;
}
void error(char *msg){
fprintf(stderr,"%s:%s\n",msg,strerror(errno));
exit(1);
}
int read_in(int socket,char *buf,int len){
char *s = buf;
int slen = len;
int c = recv(socket,s,slen,0);
while((c>0) && (s[c-1] != '\n')){//循环读取字符,直到没有字符可以读或读到了\n
s += c;slen -= c;
c = recv(socket,s,slen,0);
}
if(c <0){
return c;//防止错误
}else if(c == 0){
buf[0] = '\0';//什么也没有读到,返回一个空字符串
}else{
s[c-1] = '\0';//用\0替换\r
} return len - slen;
}
int open_listener_socket(){
//套接字描述符(互联网套接字)
int s = socket(PF_INET,SOCK_STREAM,0);
if(listener_d == -1){
error("无法打开套接字");
}
return s;
}
void bind_to_port(int socket,int port){ struct sockaddr_in name;
name.sin_family = PF_INET;
name.sin_port = (in_port_t)htons(port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
//这样设置了以后,重用端口就不会出错了
int reuse = 1;
if(setsockopt(listener_d,SOL_SOCKET,SO_REUSEADDR,(char *)&reuse,sizeof(int)) == -1){
fprintf(stderr,"无法设置套接字的”重新使用端口“选项");
}
int c = bind(socket,(struct sockaddr *)&name,sizeof(name));
if(c == -1){
error("无法绑定端口");
}
}
int send_message_to_client(int socket,char *s){ int result = send(socket,s,strlen(s),0); if(result == -1){
error("发送失败");
}
return result;
}
void handle_shutdown(int sig){
if(listener_d){
close(listener_d);
}
fprintf(stderr,"Bye Bye!\n");
exit(0);
}
int catch_signal(int sig,void (*handler)(int)){
struct sigaction action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
return sigaction(sig,&action,NULL);
}
- 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
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
编译运行
启动服务器:
~/Desktop/MyC$ gcc test.c -o test
~/Desktop/MyC$ ./test
Waiting for connection
启动一个新终端,用telnet客户端连接服务器:
成功的例子:
~$ telnet 127.0.0.1 30000
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Internet Apple Hello Protocol Server
Version 1.0
Hi ! Hi !
>Who are you?
Apple
>Apple who?
Ha Ha Stupid
Connection closed by foreign host.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
失败的例子:
~$ telnet 127.0.0.1 30000
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Internet Apple Hello Protocol Server
Version 1.0
Hi ! Hi !
>Who is that?
You showld say 'Who are you?'!Connection closed by foreign host.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
从上述运行实例,我们可以看出,服务器成功校验了客户端发送给他的数据。当你不想运行服务器,还可以使用ctrl-C关闭它,它还会跟你说bye bye哦:
~/Desktop/MyC$ ./test
Waiting for connection
^CBye Bye!
- 1
- 2
- 3
但是上面的服务器代码有一个问题,它一次只能服务一个客户端。当它正在与一个客户端连接时,其他客户端连接服务器就会出现连接不上的问题,因为服务器还在同第一个客户端通信,出现问题的终端显示:
~$ telnet 127.0.0.1 30000
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
- 1
- 2
- 3
- 4
怎么办呢?解决方案:为每一个客户端fork()一个子进程。
客户端连接到服务器以后会启用一个新创建的套接字与客户端对话,也就是说主服务器套接字可以去找下一个客户端。我们可以在客户端连接时,用fork()函数克隆一个独立的子进程来处理客户端和服务器之间的对话。
当客户端与子进程通信时,服务器的父进程可以继续连接下一个客户端。
父子进程使用不同的套接字
服务器的父进程只需要用主监听套接字(用来接受新的连接),而子进程只需要处理accpet()创建的副套接字。也就是说,父进程克隆出子进程后可以关闭副套接字,而子进程可以关闭主监听套接字。
close(connect_d);//父进程关闭副套接字
close(listener_d);//子进程关闭主监听套接字
- 1
- 2
所以这样一来,都会为每个客户端创建一个新进程,假如有无数客户端连接服务器时,计算机上就会有无数进程。如果需要控制进程的上限,可以让子进程处理完一个客户端后向父进程发出信号,利用这点,就可以用有限的子进程处理无限的客户端。
修改一下test.c里的代码:
//接受
while(1){
//创建副套接字
int connect_d = accept(listener_d,(struct sockaddr *)&client_addr,&address_size);
if(connect_d == -1){ error("无法打开副套接字");
}
if(!fork()){//如果fork()返回0,说明在子进程中 close(listener_d);
if(send_message_to_client(connect_d,"Internet Apple Hello Protocol Server \r\n Version 1.0\r\n Hi ! Hi !\r\n>") != -1){//向客户端发送数据 read_in(connect_d,buf,sizeof(buf));//从客户端读取数据 if(strncasecmp("Who are you?",buf,12)){ send_message_to_client(connect_d,"You showld say 'Who are you?'!"); }else{ if(send_message_to_client(connect_d,"Apple\r\n>") != -1){ read_in(connect_d,buf,sizeof(buf)); if(strncasecmp("Apple who?",buf,10)){ send_message_to_client(connect_d,"You should say 'Apple who?'!\r\n"); }else{ send_message_to_client(connect_d,"Ha Ha Stupid\r\n"); } } }
}
close(connect_d);//一旦通信结束,子进程就可以关闭通信套接字了
exit(0); //通信结束以后,子进程应该退出程序。这样就能防止子进程进入服务器的主循环
}
close(connect_d);//关闭套接字
}
- 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
给出完整的代码test.c:
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <signal.h>
void error(char *msg);
int read_in(int socket,char *buf,int len);
//创建互联网套接字,listener_d是套接字描述符
int open_listener_socket();
//绑定端口
void bind_to_port(int socket,int port);
//向客户端发送消息
int send_message_to_client(int socket,char *s);
//自定义SIGINT信号处理器函数
void handle_shutdown(int sig);
//捕捉SIGINT信号
int catch_signal(int sig,void (*handler)(int));
int listener_d; //套接字描述符
int main(){
//捕捉SIGINT信号,然后调用自己定义的处理器函数
if(catch_signal(SIGINT,handle_shutdown) == -1){//如果有人按了ctrl-C就调用handle_shutdown()函数
error("无法设置中断处理器");
}
//绑定:套接字描述符 套接字名
listener_d = open_listener_socket();
bind_to_port(listener_d,30000); //监听
if(listen(listener_d,10) == -1){//设置队列长度为10
error("无法监听");
}
struct sockaddr_storage client_addr;
unsigned int address_size = sizeof(client_addr);
puts("Waiting for connection");
char buf[255];
//接受
while(1){
//创建副套接字
int connect_d = accept(listener_d,(struct sockaddr *)&client_addr,&address_size);
if(connect_d == -1){
error("无法打开副套接字");
}
if(!fork()){//如果fork()返回0,说明在子进程中
close(listener_d);
if(send_message_to_client(connect_d,"Internet Apple Hello Protocol Server \r\n Version 1.0\r\n Hi ! Hi !\r\n>") != -1){//向客户端发送数据
read_in(connect_d,buf,sizeof(buf));//从客户端读取数据 if(strncasecmp("Who are you?",buf,12)){ send_message_to_client(connect_d,"You showld say 'Who are you?'!");
}else{ if(send_message_to_client(connect_d,"Apple\r\n>") != -1){ read_in(connect_d,buf,sizeof(buf)); if(strncasecmp("Apple who?",buf,10)){ send_message_to_client(connect_d,"You should say 'Apple who?'!\r\n"); }else{ send_message_to_client(connect_d,"Ha Ha Stupid\r\n"); } }
}
}
close(connect_d);//一旦通信结束,子进程就可以关闭通信套接字了
exit(0); //通信结束以后,子进程应该退出程序。这样就能防止子进程进入服务器的主循环
}
close(connect_d);//关闭套接字
}
return 0;
}
void error(char *msg){
fprintf(stderr,"%s:%s\n",msg,strerror(errno));
exit(1);
}
int read_in(int socket,char *buf,int len){
char *s = buf;
int slen = len;
int c = recv(socket,s,slen,0);
while((c>0) && (s[c-1] != '\n')){//循环读取字符,直到没有字符可以读或读到了\n
s += c;slen -= c;
c = recv(socket,s,slen,0);
}
if(c <0){
return c;//防止错误
}else if(c == 0){
buf[0] = '\0';//什么也没有读到,返回一个空字符串
}else{
s[c-1] = '\0';//用\0替换\r
} return len - slen;
}
int open_listener_socket(){
//套接字描述符(互联网套接字)
int s = socket(PF_INET,SOCK_STREAM,0);
if(listener_d == -1){
error("无法打开套接字");
}
return s;
}
void bind_to_port(int socket,int port){ struct sockaddr_in name;
name.sin_family = PF_INET;
name.sin_port = (in_port_t)htons(port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
//这样设置了以后,重用端口就不会出错了
int reuse = 1;
if(setsockopt(listener_d,SOL_SOCKET,SO_REUSEADDR,(char *)&reuse,sizeof(int)) == -1){
fprintf(stderr,"无法设置套接字的”重新使用端口“选项");
}
int c = bind(socket,(struct sockaddr *)&name,sizeof(name));
if(c == -1){
error("无法绑定端口");
}
}
int send_message_to_client(int socket,char *s){ int result = send(socket,s,strlen(s),0); if(result == -1){
error("发送失败");
}
return result;
}
void handle_shutdown(int sig){
if(listener_d){
close(listener_d);
}
fprintf(stderr,"Bye Bye!\n");
exit(0);
}
int catch_signal(int sig,void (*handler)(int)){
struct sigaction action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
return sigaction(sig,&action,NULL);
}
- 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
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
编译运行:
~/Desktop/MyC$ ./test
Waiting for connection
|
- 1
- 2
- 3
这样一来,即使第一个客户端还在和服务器通信,其他客户端仍然可以连接服务器。
文章来源: blog.csdn.net,作者:WongKyunban,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/weixin_40763897/article/details/87724562
- 点赞
- 收藏
- 关注作者
评论(0)