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编程流程

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

编译客户端程序

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

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

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

​ ./server1

编译服务器端程序

4、程序运行效果

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

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

客户端运行效果

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

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

服务器端运行效果

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
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
#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
#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/07/01/Linux-socket-communication-programming/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.