第十四夜:网络编程2-Socket通信

UDP套接字

在前一夜的复习中实现了一个C/S架构的TCP套接字demo。今天首先通过创建一个UDP的套接字例子来区分UDP Socket和TCP Socket的使用区别。

客户端:

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
/*
* 源码出处:https://blog.csdn.net/htianlong/article/details/7560841
* 编译:gcc -m32 -o client client.c
* 用法:sudo ./client localhost AAAA 8888
*/
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>


#define BUFFSIZE 255
void Die(char *mess) { perror(mess); exit(1); }


int main(int argc, char *argv[]) {
int sock;
struct sockaddr_in echoserver;
struct sockaddr_in echoclient;
struct hostent *host; /* host information */
struct in_addr h_addr; /* Internet address */

char buffer[BUFFSIZE];

unsigned int echolen, clientlen;
int received = 0;

if (argc != 4) {
fprintf(stderr, "USAGE: %s <server> <word> <port>\n", argv[0]);
exit(1);
}

/* Create the UDP socket */
if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
Die("Failed to create socket");
}

if ((host = gethostbyname(argv[1])) == NULL) {
Die("Host address look up failed");
}

h_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
//printf("Address :%s\n", inet_ntoa(h_addr));

/* Construct the server sockaddr_in structure */
memset(&echoserver, 0, sizeof(echoserver)); /* Clear struct */
echoserver.sin_family = AF_INET; /* Internet/IP */
echoserver.sin_addr.s_addr = h_addr.s_addr;
echoserver.sin_port = htons(atoi(argv[3])); /* server port */

/* Send the word to the server */
echolen = strlen(argv[2]);
if (sendto(sock, argv[2], echolen, 0, (struct sockaddr *) &echoserver, sizeof(echoserver)) != echolen) {
Die("Mismatch in number of sent bytes");
}
}

服务端:

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
/*
* 源码出处:https://blog.csdn.net/htianlong/article/details/7560841
* 编译:gcc -m32 -o server server.c
* 用法:sudo ./server 8888
*/
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define BUFFSIZE 255
void Die(char *mess) { perror(mess); exit(1); }

int main(int argc, char *argv[]) {
int sock;
struct sockaddr_in echoserver;
struct sockaddr_in echoclient;
char buffer[BUFFSIZE];
unsigned int echolen, clientlen, serverlen;
int received = 0;

if (argc != 2) {
fprintf(stderr, "USAGE: %s <port>\n", argv[0]);
exit(1);
}

/* Create the UDP socket */
if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
Die("Failed to create socket");
}

/* Construct the server sockaddr_in structure */
memset(&echoserver, 0, sizeof(echoserver)); /* Clear struct */
echoserver.sin_family = AF_INET; /* Internet/IP */
echoserver.sin_addr.s_addr = htonl(INADDR_ANY); /* Any IP address */
echoserver.sin_port = htons(atoi(argv[1])); /* server port */

/* Bind the socket */
serverlen = sizeof(echoserver);
if (bind(sock, (struct sockaddr *) &echoserver, serverlen) < 0) {
Die("Failed to bind server socket");
}

/* Run until cancelled */
while (1) {
/* Receive a message from the client */
clientlen = sizeof(echoclient);
if ((received = recvfrom(sock, buffer, BUFFSIZE, 0, (struct sockaddr *) &echoclient, &clientlen)) < 0) {
Die("Failed to receive message");
}

fprintf(stderr, "Client %s connected:\nMessage:%s\n", inet_ntoa(echoclient.sin_addr),buffer);
/* Send the message back to client */
if(sendto(sock, buffer, received, 0, (struct sockaddr *) &echoclient, sizeof(echoclient)) != received) {
Die("Mismatch in number of echo'd bytes");
}
}
}

TCP Socker Server

本例以一个简单的http server来演示socket server构建:

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
/*
* 编译:gcc -m32 -o http_server http_server.c
* 运行:./http_server 8080
*/
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define MAXPENDING 5 /* Max connection requests */
#define BUFFSIZE 32

void Die(char *mess) { perror(mess); exit(1); }

void HandleClient(int sock) {
char buffer[BUFFSIZE];
char response[] = "HTTP/1.1 200 OK\nConnection: close\n\n<html><head><title>Test</title></head><body><h1>Test</h1></body></html>\n\n";
send(sock, response, sizeof(response), 0);
close(sock);
}


int main(int argc, char *argv[]) {
int serversock, clientsock;
struct sockaddr_in echoserver, echoclient;

if (argc != 2) {
fprintf(stderr, "USAGE: echoserver <port>\n");
exit(1);
}
/* Create the TCP socket */
if ((serversock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
Die("Failed to create socket");
}
/* Construct the server sockaddr_in structure */
memset(&echoserver, 0, sizeof(echoserver)); /* Clear struct */
echoserver.sin_family = AF_INET; /* Internet/IP */
echoserver.sin_addr.s_addr = htonl(INADDR_ANY); /* Incoming addr */
echoserver.sin_port = htons(atoi(argv[1])); /* server port */

/* Bind the server socket */
if (bind(serversock, (struct sockaddr *) &echoserver,
sizeof(echoserver)) < 0) {
Die("Failed to bind the server socket");
}

/* Listen on the server socket */
if (listen(serversock, MAXPENDING) < 0) {
Die("Failed to listen on server socket");
}

/* Run until cancelled */
while (1) {
unsigned int clientlen = sizeof(echoclient);
/* Wait for client connection */
if ((clientsock = accept(serversock, (struct sockaddr *) &echoclient, &clientlen)) < 0) {
Die("Failed to accept client connection");
}

fprintf(stdout, "Client connected: %s\n", inet_ntoa(echoclient.sin_addr));
HandleClient(clientsock);
}
}

阻塞套接字与非阻塞套接字

在阻塞的套接字中,服务端接收到的请求需要排队对其进行处理,这就形成了套接字阻塞。为了解决套接字阻塞的问题,可以创建非阻塞的服务端模型。
一个在网上找的非阻塞套接字的例子:

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
/***************************************************************************
* 文件名称:select_socket.c
* 源码出处:https://blog.csdn.net/pinkbean/article/details/71937646
* 文件作用:这个文件的作用是用来编写一个单进程,单线程的多客户的套接字的客户端
****************************************************************************/

#include <stdio.h> //引入stdio.h头文件,这个头文件中包含了Linux上相关的IO流操作函数
#include <stdlib.h> //引入stdlib.h头文件,这个头文件中包含了Linux下的一些标准的函数
#include <string.h> //引入string,h头文件,这个头文件中包含了字符串操作的相关函数
#include <errno.h> //引入errno.h头文件,这个头文件中包含了错误处理的相关函数
#include <sys/types.h> //引入sys/目录下面的types.h头文件,这个头文件中包含了相关类型的定义
#include <sys/socket.h> //引入sys/目录下面的socket.h头文件,这个头文件中包含了套接字相关的头文件
#include <sys/time.h> //引入sys/目录下面的time.h头文件,这个头文件中包含了时间操作的相关函数和相关的结构体
#include <netinet/in.h> //引入netinet.h/目录下面的in.h头文件,这个头文件中包含了网络字节序列转换的相关函数,还有相关的网域的宏定义
#include <unistd.h>
#include <ctype.h>

int main(int argc, char* argv[]){
//开始定义要使用的相关变量-------------------------
int ret; //定义一个int类型的数据,这个int类型的数据用来保存函数的运行结果
int server_socket; //定义一个int类型的数据用来保存本地建立的服务端的套接字
int conn_socket; //定义一个int类型的数据用来保存客户端和本地连接好的套接字
char RecvBuffer[1024]; //定义一个char类型的字符串,这个字符串用来保存从客户端接收到的数据
char SendBuffer[1024]; //定义一个char类型的字符串,这个字符串用来保存要向客户端发送的数据
struct sockaddr_in server_address; //定义一个sockaddr_in类型的结构体,这个结构体来保存你服务端的相关地址
struct sockaddr_in conn_address; //定义一个sockaddr_in类型的结构体,这个结构体用来保存连接服务端之后的客户端的地址信息
fd_set readfds, readcpyfds; //定义两个文件描述集合

//将相关的变量进行初始化---------------------------
memset(RecvBuffer, 0x00, sizeof(RecvBuffer));
memset(SendBuffer, 0x00, sizeof(SendBuffer));
memset(&server_address, 0x00, sizeof(server_address));
memset(&conn_address, 0x00, sizeof(conn_address));

//开始命名套接字,并且建立监听队列---------------
server_socket = socket(AF_INET, SOCK_STREAM, 0); //创建一个因特网下的流套接字,协议默认定义
server_address.sin_family = AF_INET; //地址使用的协议簇为AF_INET
server_address.sin_addr.s_addr = htonl(INADDR_ANY); //定义这个服务端的套接字的可以接受的地址为任意地址,并且使用htonl将地址转化为网络字节序列
server_address.sin_port = htons(8888); //将端口转化为网络字节序列,监听8888端口

bind(server_socket, (struct sockaddr*)&server_address, sizeof(server_address)); //使用bind函数为套接字来进行命名
listen(server_socket, 10); //为我们创建的本地套接字创建监听队列,这个队列长为10个连接数

//开始操作文件描述符集合---------------------------
FD_ZERO(&readfds); //使用FD_ZERO()函数将readfds文件描述符集合清空
FD_SET(server_socket, &readfds); //将server_socket这个文件描述符添加到readfds

//开始使用while()循环来接收连接--------------------
while(1){
readcpyfds = readfds; //将readfds进行一次备份

/*使用select()函数来监控文件描述符集合readcpyfds,如果这个集合中的任意一个文件描述符变为可写的状态select就返回,
*因为我们不需要可写和错误的文件描述符,就直接将2,3参数设置为0(0表示空,还进行了强制类型转换),同时我们将
*时间设置为空,表示select将阻塞*/
ret = select(FD_SETSIZE, &readcpyfds, (fd_set*)0, (fd_set*)0, (struct timeval*)0);
if(ret < 1)
{
fprintf(stderr, "select error!\n"); //输出错误信息
exit(EXIT_FAILURE); //进程失败退出
}

int fd; //定义一个整形变量,这个整形变量用来控制循环
for(fd = 0; fd < FD_SETSIZE; fd++) //使用for循环来对文件描述符进行检索和操作,FD_SETSIZE为文件描述符集合的最大容量
{
if(FD_ISSET(fd, &readcpyfds)) //如果fd为testfds中设置的文件描述符
{
if(fd == server_socket) //如果fd == server_socket,表示是有客户端要连接服务端
{
int size = sizeof(conn_address); //得到结构体的大小
conn_socket = accept(server_socket, (struct sockaddr*)&conn_address, &size); //使用accept()函数来接受客户端传来的连接,将客户端连接的地址的信息保存到conn_address中
FD_SET(conn_socket, &readcpyfds); //将conn_sockfd这个已经连接好的套接字描述符保存到文件描述符集合中readcpyfds这个集合中
printf("We have recved a connection!\n"); //输出信息
}
else //如果不是server_socket那就表示是已经创建好连接的套接字字符,我们开始接受客户端传过来的数据,再将数据传回去
{
ret = recv(fd, RecvBuffer, sizeof(RecvBuffer), 0); //使用recv()函数将把从建立好的连接的数据保存到RecvBuffer中
if(ret < 0){
fprintf(stderr, "recv error!, The error is %s, errno is %d\n", strerror(errno), errno);
exit(EXIT_FAILURE); //进程异常退出
}
else if(ret == 0) //ret为0表示超时
{
printf("recv() time over!\n"); //输出超时
exit(EXIT_FAILURE); //进程异常退出
}
printf("The data we recve is %s\n", RecvBuffer); //输出我们接收到的数据

char* ptr; //定义一个char*指针,这个指针这个指针用来转化大小写
for(ptr = RecvBuffer; *ptr; ptr++) //将字符串中的数据转化为大写
{
*ptr = toupper(*ptr); //将小写转化为大写
}
strcpy(SendBuffer, RecvBuffer); //将RecvBuffer中的数据复制到SendBuffer中

send(fd, SendBuffer, strlen(SendBuffer), 0); //将SendBuffer中的数据发送回fd这个建立好连接的套接字中
FD_CLR(fd, &readfds); //将readfds中的fd这个文件描述符清除掉,以防在副本中出现多余的文件描述符
memset(RecvBuffer, 0x00, sizeof(RecvBuffer)); //将接受数据的字符串和发送数据的字符串清空
memset(SendBuffer, 0x00, sizeof(SendBuffer));
close(fd); //关闭套接字的连接
}
}
}
}
}

多线程非阻塞套接字

待后续补充。

参考资料

Linux socket 编程,第一部分
Linux socket 编程,第二部分
Linux下的socket演示程序
编写一个单进程非阻塞多客户的套接字客户端

第十五夜:网络编程3-常见的应用层协议 第十三夜:网络编程1-网络协议及其数据结构
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×