C语言——网络与套接字

举报
yd_221104950 发表于 2020/12/03 00:42:05 2020/12/03
【摘要】 互联网中大部分的底层网络代码都是用C语言写的。网络程序通常由两部分程序组成:服务器和客户端。 服务器将同时与多个客户端通信。客户端与服务器之间将展开一段结构化对话,叫做协议。 互联网使用了各种协议,一部分是低层协议,另一部分是高层协议。 低层协议有IP,它用来控制二进制的0和1在互联网中的发送方式。 高层协议有HTTP,它用来控制浏览器与网络服务器的对话。 协议通...

互联网中大部分的底层网络代码都是用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

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。