Linux socket 通信编程

示例:用C语言,基于linux提供的socket函数,编写一个实现“从客户端输入十个字符串,在服务器端完成字符数和单词数统计,并按首位字母排序”的远程计算的网络服务程序。

1、UDP编程说明

UDP协议的服务器端流程:

(1)建立套接字文件描述符,使用函数socket(),生成套接字文件描述符。

(2)设置服务器地址和侦听端口,初始化要绑定的网络地址结构。

(3)绑定侦听端口,使用bind()函数,将套接字文件描述符和一个地址类型变量进行绑定。

(4)接收客户端的数据,使用recvfrom()函数接收客户端的网络数据。

(5)向客户端发送数据,使用sendto()函数向服务器主机发送数据。

(6)关闭套接字,使用close()函数释放资源。UDP协议的客户端流程

UDP协议的客户端流程:

(1)建立套接字文件描述符,socket();

(2)设置服务器地址和端口,struct sockaddr;

(3)向服务器发送数据,sendto();

(4)接收服务器的数据,recvfrom();

(5)关闭套接字,close()。

UDP.png

UDP编程流程

2、相关函数

(1) int socket(AF_INET, SOCK_DGRAM, 0);

创建udp socket,返回套接字描述符,UDP协议建立套接字的方式同TCP方式一样,使用socket()函数,只不过协议的类型使用SOCK_DGRAM,而不是SOCK_STREAM。

(2) int sendto(int sockfd, const void *data, int data_len, unsigned int flags, struct sockaddr *remaddr,sock_lenremaddr_len)

功能:基于UDP发送数据报,返回实际发送的数据长度,出错时返回-1

参数说明:

sockfd:套接字描述符

data:指向要发送数据的指针

data_len:数据长度

flags:通常为0

remaddr:远端地址:IP地址和端口号

remaddr_len:地址长度

(3) int recvfrom(int sockfd, void *buf,int buf_len,unsigned int flags,struct sockaddr *from,sock_len *fromlen);

功能:从UDP接收数据,返回实际接收的字节数,失败时返回-1

参数说明:

Sockfd:套接字描述符

buf:指向内存块的指针

buf_len:内存块大小,以字节为单位

flags:一般为0

from:远端的地址,IP地址和端口号

fromlen:远端地址长度

3、程序编写

(1) 用C语言编写客户端程序,创建一个socket通信,从客户端输入十个字符串。

(2) 编译客户端程序生成可执行文件

输入命令:gcc client1.c -o client1

​ ./client1

client.png

编译客户端程序

(3) 用C语言编写服务器端程序,绑定客户端通信,接收从客户端输入的十个字符串,显示在屏幕上,并统计输入的字符串中总的单词数和字符数,并按首位字母的ASCII码值从高到低进行排列,输出显示到屏幕。

(4) 编译服务器端程序生成可执行文件

输入命令:gcc server1.c -o server1

​ ./server1

server.png

编译服务器端程序

4、程序运行效果

(1) 客户端输入十个字符串。

注:输入10个字符串时每行代表1个字符串,10字符串10行,由于字符串的输入是由 fgets() 函数实现的,所以获取字符串的时候会将空格符和换行符也进行保存,这点在后面进行总字符数的统计时也体现了出来,获取的字符串由结构体sendbuf[10]进行存储。

client_work.png

客户端运行效果

(2) 服务器端接收数据,显示在屏幕上,并统计输入的字符串中总的单词数和字符数,并按首位字母的ASCII码值从高到低进行排列,输出显示到屏幕。

注:统计总单词数相当于统计总空格数,空格数+1即为总单词数,字符数的统计结果包括了每个字符串的空格和换行符,每个字符串都有一个换行符,如图9,可以看到得到的总字符数85中包含了所有的空格符和换行符。利用冒泡排序,直接比较输入的10个字符串的首字母,实现字符串按首字母ASCII码值从大到小排列。

server_work.png

服务器端运行效果

5、字符串排序说明

(1) 实现将10个字符串按首字母的ASCII码值从高到低进行排列并依次输出显示在屏幕上。

功能实现:输入的10个字符串设置结构体sendbuf[10]保存,再服务器端设置结构体recvbuf[10]接收数据,利用冒泡排序,先将每个字符串的首字母取出来,将其值赋给服务端接收缓冲区结构体的recvbuf[i].init成员(专门用于存储字符串的首字母),再直接比较recvbuf[i].init成员值的ASCII码值大小,实现从高到底排列并输出到屏幕上。

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
for (int n = 0; n < 10; n++)

​ {

​ int k = n;

​ for (int m = n + 1; m < 10; m++)

​ {

​ if (recvbuf[k].init < recvbuf[m].init)

​ k = m;

​ }

​ if (k != n)

​ {

​ temp = recvbuf[n];

​ recvbuf[n] = recvbuf[k];

​ recvbuf[k] = temp;

​ }

​ }

​ printf("字符串按首字母ASCII码值从大到小排列为:\n");

​ for (int n = 0; n < 10; n++)

​ {

​ printf("%s", recvbuf[n].string_client);

​ }

6、完整客户端、服务器端程序代码

客户端程序源码:

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
#include <unistd.h>

\#include <sys/types.h>

\#include <sys/socket.h>

\#include <netinet/in.h>

\#include <arpa/inet.h>

\#include <stdlib.h>

\#include <stdio.h>

\#include <errno.h>

\#include <string.h>

\#define MYPORT 8886

char* SERVERIP = "127.0.0.1";

\#define ERR_EXIT(m) \

​ do \

{ \

​ perror(m); \

​ exit(EXIT_FAILURE); \

​ } while(0)

void echo_cli(int sock)

{

​ struct sockaddr_in servaddr;

​ memset(&servaddr, 0, sizeof(servaddr));

​ servaddr.sin_family = AF_INET;

​ servaddr.sin_port = htons(MYPORT);

​ servaddr.sin_addr.s_addr = inet_addr(SERVERIP);

​ int ret;

​ struct client

​ {

​ char string_client[100];

​ }sendbuf[10];

​ struct server

​ {

​ char string_client[100];

​ }recvbuf[10];

​ //struct client temp;

​ //struct server temp;

​ int i;

​ printf("请输入10字符串:\n");

​ for (i = 0; i < 10; i++)

​ {

​ fgets(sendbuf[i].string_client,100,stdin);

​ }

​ printf("向服务器发送:\n");

​ for (i=0; i<10; i++)

​ {

​ printf("%s", sendbuf[i].string_client);

​ sendto(sock, sendbuf[i].string_client, strlen(sendbuf[i].string_client), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));

​ memset(sendbuf, 0, sizeof(sendbuf[i].string_client));

​ }

​ close(sock);

}

int main(void)

{

​ int sock;

​ if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)

​ ERR_EXIT("socket");

​ echo_cli(sock);

​ 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
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#include<stdio.h>

\#include<stdlib.h>

\#include<unistd.h>

\#include<errno.h>

\#include<sys/types.h>

\#include<sys/socket.h>

\#include<netinet/in.h>

\#include<string.h>

\#define MYPORT 8886

\#define ERR_EXIT(m) \

​ do { \

​ perror(m); \

​ exit(EXIT_FAILURE); \

​ } while (0)

void echo_ser(int sock)

{

​ //char recvbuf[1024] = {0};

​ struct server

​ {

​ char string_client[100];

​ char init;

​ }recvbuf[10];

​ struct server temp;

​ struct sockaddr_in peeraddr;

​ socklen_t peerlen;

​ int n;

​ int i, nword=0, nchar=0;

​ for (i=0; i<10; i++)

​ {

​ peerlen = sizeof(peeraddr);

​ memset(recvbuf[i].string_client, 0, sizeof(recvbuf[i].string_client));

​ n = recvfrom(sock, recvbuf[i].string_client, sizeof(recvbuf[i].string_client), 0,

​ (struct sockaddr *)&peeraddr, &peerlen);

​ if (n <= 0)

​ {

​ if (errno == EINTR)

​ continue;

​ ERR_EXIT("recvfrom error");

​ }

​ else if(n > 0)

​ {

​ printf("接收到的数据:%s",recvbuf[i].string_client);

​ //printf("%s",recvbuf[i].string_client);

​ //sendto(sock, recvbuf, n, 0,

​ // (struct sockaddr *)&peeraddr, peerlen);

​ //printf("回送的数据:%s\n",recvbuf[i].string_client);

​ }

​ }

​ int numWhiteSpace = 0;

​ int j = 0;

​ for (int n = 0; n < 10; n++)

​ {

​ char str[100];

​ strcpy(str, recvbuf[n].string_client);

​ //printf("字符串:%s\n", str);

​ recvbuf[n].init = str[0];

​ while ('\0' != str[j])

​ {

​ if (' ' == str[j]){

​ ++numWhiteSpace; //空格数

​ }

​ ++j; //字符数

​ }

​ nchar = j + nchar;

​ nword = numWhiteSpace + 1+nword;

​ j = 0;

​ numWhiteSpace = 0;

​ }

​ printf("输入字符串的总单词数:%d\n", nword);

​ printf("输入字符串的总字符数(包括空格、换行符):%d\n", nchar);

​ for (int n = 0; n < 10; n++)

​ {

​ int k = n;

​ for (int m = n + 1; m < 10; m++)

​ {

​ if (recvbuf[k].init < recvbuf[m].init)

​ k = m;

​ }

​ if (k != n)

​ {

​ temp = recvbuf[n];

​ recvbuf[n] = recvbuf[k];

​ recvbuf[k] = temp;

​ }

​ }

​ printf("字符串按首字母ASCII码值从大到小排列为:\n");

​ for (int n = 0; n < 10; n++)

​ {

​ printf("%s", recvbuf[n].string_client);

​ }

​ close(sock);

}

int main(void)

{

​ int sock;

​ if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)

​ ERR_EXIT("socket error");

​ struct sockaddr_in servaddr;

​ memset(&servaddr, 0, sizeof(servaddr));

​ servaddr.sin_family = AF_INET;

​ servaddr.sin_port = htons(MYPORT);

​ servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

​ printf("监听%d端口\n",MYPORT);

​ if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)

​ ERR_EXIT("bind error");

​ echo_ser(sock);

​ return 0;

}
Author: wnxy
Link: http://www.wnxy.xyz/2019/12/26/Linux%20%20socket%E9%80%9A%E4%BF%A1%E7%BC%96%E7%A8%8B/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.