C语言基于UDP的简易聊天程序

  用C语言实现的UDP文本聊天程序。理解基本的UDP编程原理。为自己实验课的一次整理。

什么是UDP

  UDP:用户数据包协议(User Datagram Protocol,缩写为UDP),又称用户数据报文协议,是一个简单的面向数据报的传输层协议 。

实现内容

客户端

  • 以命令行的方式启动,命令行要求有服务器地址,服务器端口号;
  • 建立套接字,可以输入文本与服务器进行通信。要求客户端可以连续发送多条文本信息,而不同等待接收到服务器端的信息后再发送。客户端也可以连续接收服务器端的多条文本信息,而不用等待客户端键盘输入后再接收。

服务器

  • 服务器如果收到客户端方的exit文本,则退出服务器程序;
  • 服务器端程序收到从键盘输入的shutdown文本,也退出程序。

基本函数和数据结构

套接字地址结构类型

1
2
3
4
5
6
struct sockaddr_in {
short int sin_family; /*地址族:AF_INET*/
unsigned short int sin_port;/*端口号*/
struct in_addr sin_addr;/*IP地址*/
unsigned char sin_zero[8];/*零数据(bzero或memset设置)*/
};

字节序转网络

  • 主机转网络:
1
2
uint16_t htons(uint16_t hostshort)
uint32_t htonl(uint32_t hostlong)
  • 网络转主机:
1
2
uint16_t ntohs(uint16_t netshort)
uint32_t ntohl(uint32_t netlong)
  • IP地址网络字节和字符串转换函数:
1
2
inet_addr()、inet_aton或inet_pton();/*将IP字符串转换成网络字节序的二进制*/
inet_ntoa()或inet_ntop();/*将网络地址转换成IP字符串*/

基本套接字函数:

1
2
3
4
5
创建套接字函数:int socket( int domain, int type, int protocol)
绑定套接字函数:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
发送数据套接字函数:ssize_t sendto(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen)
接收数据套接字函数:ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen)
关闭套接字函数:int close(int sockfd)

实现代码

服务器代码 server.c

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
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <errno.h>
#include <termios.h>

#define QUEUE 20
#define BUFFER_SIZE 1024

// 键盘输入检测程序
int kbhit(void)
{
struct termios oldt, newt;
int ch;
int oldf;

tcgetattr(STDIN_FILENO, &oldt);

newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);

tcsetattr(STDIN_FILENO, TCSANOW, &newt);

oldf = fcntl(STDIN_FILENO, F_GETFL, 0);

fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);

ch = getchar();

tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
fcntl(STDIN_FILENO, F_SETFL, oldf);

if (ch != EOF)
{
//ungetc(ch, stdin);
return 1;
}
return 0;
}

int main(int argc, char **argv)
{
char recvbuf[BUFFER_SIZE];
char sendbuf[BUFFER_SIZE];

// 将命令行中的第一参数设置为server的端口号
int MYPORT = atoi(argv[1]);

// 创建server的套接字
int server_sockfd = socket(AF_INET, SOCK_DGRAM, 0);

// 定义server的sockaddr_in
struct sockaddr_in server_sockaddr;
bzero(&server_sockaddr, sizeof(server_sockaddr));
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(MYPORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

int sin_len = sizeof(server_sockaddr);

// bind,成功返回0,出错返回-1
if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1)
{
perror("bind");
exit(1);
}

while (1)
{
memset(recvbuf, 0, sizeof(recvbuf));
memset(sendbuf, 0, sizeof(sendbuf));

int len = recvfrom(server_sockfd, recvbuf, sizeof(recvbuf), MSG_DONTWAIT, (struct sockaddr *)&server_sockaddr, &sin_len);

if (len > 0) // 成功收到消息
{
if (strcmp(recvbuf, "exit\n") == 0) // 收到客户端传来的exit则退出
{
printf("收到客户端的退出指令");
break;
}
else
{
printf("来自客户端数据:%s\n", recvbuf);
}
}
else //如果出错是由于没有数据则继续,否则退出
{
if (errno != EAGAIN)
{
perror("接收失败");
exit(1);
}
}

// 向客户端发送数据
if (kbhit()) //如果检测到键盘有操作
{
fgets(sendbuf, sizeof(sendbuf), stdin);

if (strcmp(sendbuf, "shutdown\n") == 0) // 服务器收到键盘输入的shutdown就关闭服务程序
break;
// 发送
sendto(server_sockfd, sendbuf, sizeof(sendbuf), MSG_DONTWAIT, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr));
printf("发送给客户端数据:%s\n", sendbuf);
}
}

// 关闭
close(server_sockfd);

return 0;
}

客户端代码 client.c

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 <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <errno.h>
#include <termios.h>

#define BUFFER_SIZE 1024

// 键盘输入检测程序
int kbhit(void)
{
struct termios oldt, newt;
int ch;
int oldf;

tcgetattr(STDIN_FILENO, &oldt);

newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);

tcsetattr(STDIN_FILENO, TCSANOW, &newt);

oldf = fcntl(STDIN_FILENO, F_GETFL, 0);

fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);

ch = getchar();

tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
fcntl(STDIN_FILENO, F_SETFL, oldf);

if (ch != EOF)
{
//ungetc(ch, stdin);
return 1;
}
return 0;
}

int main(int argc, char **argv)
{
char sendbuf[BUFFER_SIZE];
char recvbuf[BUFFER_SIZE];

// 将第一个参数设为ip
char *SERVER_IP = argv[1];
// 将第二个参数设为端口号
int MYPORT = atoi(argv[2]);

// 创建client的套接字
int client_sockfd = socket(AF_INET, SOCK_DGRAM, 0);

// 定义client的sockaddr_in
struct sockaddr_in client_sockaddr;
bzero(&client_sockaddr, sizeof(client_sockaddr));
client_sockaddr.sin_family = AF_INET;
client_sockaddr.sin_port = htons(MYPORT); // 服务器端口
client_sockaddr.sin_addr.s_addr = inet_addr(SERVER_IP); // 服务器ip

int sin_len = sizeof(client_sockaddr);

while (1)
{
memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));

if (kbhit()) // 检测键盘是否有输入
{
fgets(sendbuf, sizeof(sendbuf), stdin);
// 输入shutdown关闭客户端
if (strcmp(sendbuf, "shutdown\n") == 0)
break;
printf("向服务器发送数据:%s\n", sendbuf);
// 发送
sendto(client_sockfd, sendbuf, strlen(sendbuf), MSG_DONTWAIT, (struct sockaddr *)&client_sockaddr, sizeof(client_sockaddr));
// 收到键盘输入exit关闭客户程序和服务器
if (strcmp(sendbuf, "exit\n") == 0 || strcmp(sendbuf, "shutdown\n") == 0)
break;
}

int len = recvfrom(client_sockfd, recvbuf, sizeof(recvbuf), MSG_DONTWAIT, (struct sockaddr *)&client_sockaddr, &sin_len);

if (len > 0) //成功接收数据
{
printf("从服务器接收数据:%s\n", recvbuf);
}
else // 如果出错是因为没有数据则继续,否则退出
{
if (errno != EAGAIN)
{
perror("连接失败");
exit(1);
}
}
}

// 关闭
close(client_sockfd);

return 0;
}
打赏点猫粮钱吧~