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

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

什么是TCP

  TCP:传输控制协议(Transmission Control Protocol,TCP),是一种面向连接的、可靠的、基于字节流的传输层通信协议。

实现内容

客户端

建立一个用TCP连接的客户端,以命令行的方式启动,命令行要求有服务器地址,服务器端口号。连接建立成功后可以输入文本与服务器进行通信。客户方通过输入exit文本结束与服务器的通信并退出程序。

服务器

服务器程序等待客户端的连接,客户连接成功后可以与客户端进行文字通信。服务器如果收到客户端方的exit文本,则退出服务器程序。

基本函数和数据结构

套接字地址结构类型

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
6
7
8
创建套接字函数:int socket( int domain, int type, int protocol)
绑定套接字函数:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
建立套接字函数:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
监听套接字函数:int listen(int sockfd, int backlog)
接收请求套接字函数:int accept(int sockfd, struct sockaddr *addr, socklen_t addrlen)
发送数据套接字函数:int send(int sockfd, const void *buf, size_t len, int flags)
接收数据套接字函数:int recv(int sockfd, const void *buf, size_t len, int flags)
关闭套接字函数: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
#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>

#define QUEUE 20
#define BUFFER_SIZE 1024

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

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

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

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

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

// listen,成功返回0,出错返回-1
if(listen(server_sockfd, QUEUE) == -1)
{
perror("listen");
exit(1);
}

// 客户端套接字
char buffer[BUFFER_SIZE];
struct sockaddr_in client_sockaddr;
socklen_t length = sizeof(client_sockaddr);
char sendbuf[BUFFER_SIZE];

printf("等待客户端连接\n");

// 接受客户端请求,成功返回非负描述字,出错返回-1
int conn = accept(server_sockfd, (struct sockaddr*)&client_sockaddr, &length);
if(conn < 0)
{
perror("connect");
exit(1);
}
printf("客户端成功连接\n");

while(1)
{
memset(buffer, 0, sizeof(buffer));
memset(sendbuf, 0, sizeof(sendbuf));
int len = recv(conn, buffer, sizeof(buffer), 0);

// 客户端发送exit或者异常结束时,退出
if(strcmp(buffer, "exit\n") == 0 || len <= 0)
break;
printf("来自客户端数据:%s\n", buffer);

// 向客户端发送数据
fgets(sendbuf, sizeof(sendbuf), stdin);
send(conn, sendbuf, strlen(sendbuf), 0);
printf("发送给客户端数据:%s\n", sendbuf);
}

close(conn);
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
#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>

#define BUFFER_SIZE 1024

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

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

// 定义client的sockaddr_in
struct sockaddr_in client_sockaddr;
memset(&client_sockaddr, 0, 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

printf("连接%s:%d\n", SERVER_IP,MYPORT);

// 连接服务器,成功返回0,错误返回-1
if (connect(client_sockfd, (struct sockaddr *)&client_sockaddr, sizeof(client_sockaddr)) < 0)
{
perror("connect");
exit(1);
}
printf("服务器连接成功\n");

char sendbuf[BUFFER_SIZE];
char recvbuf[BUFFER_SIZE];

while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
printf("向服务器发送数据:%s\n",sendbuf);
send(client_sockfd, sendbuf, strlen(sendbuf), 0); // 发送
if(strcmp(sendbuf,"exit\n")==0)
break;
recv(client_sockfd, recvbuf, sizeof(recvbuf), 0); // 接收
printf("从服务器接收数据:%s\n", recvbuf);

memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
}

close(client_sockfd);

return 0;
}

运行结果

image

打赏点猫粮钱吧~