0%

计算机网络实验二_chat实验

计算机网络实验2_Chat实验

实验要求

利用客户/服务器(Client/Sever或CS)模式实现一个多人聊天(群聊)程序。其功能是每个客户发送给服务器的消息都会传送给所有的客户端。

实验环境

采用linux编程,调用pthread库

实验内容

1.编写多人聊天程序,要求客户端和服务器都采用多线程方式进行编程。每个客户端都采用TCP协议连接服务器并保持连接。服务器同时与所有客户端建立和保持连接。每个客户端输入的消息都会通过服务器转发给所有客户。

客户端程序:

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

#define BUFLEN 2000 // 缓冲区大小
/*------------------------------------------------------------------------
* main - TCP client for TIME service
*------------------------------------------------------------------------
*/

void* client_recv(void* in){
int my_sock=(long)in;
char* buf=new char[BUFSIZ+1];
while(1){
int c1=recv(my_sock,buf,BUFSIZ,0);
if(c1==0){
return NULL;
}
buf[c1]='\0';
printf("message received: %s\n",buf);
}
}

int main(int argc, char *argv[])
{
char *host = "127.0.0.1"; /* server IP to connect */
char *service = "50500"; /* server port to connect */
struct sockaddr_in sin; /* an Internet endpoint address */
char buf[BUFLEN+1]; /* buffer for one line of text */
int sock; /* socket descriptor */
int cc; /* recv character count */
pthread_t thread_handle;
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //创建套接字,参数:因特网协议簇(family),流套接字,TCP协议
//返回:要监听套接字的描述符或INVALID_SOCKET
memset(&sin, 0, sizeof(sin)); // 从&sin开始的长度为sizeof(sin)的内存清0
sin.sin_family = AF_INET; // 因特网地址簇
sin.sin_addr.s_addr = inet_addr(host);
// printf("%d\n",sin.sin_addr.s_addr); // 设置服务器IP地址(32位)
sin.sin_port = htons((unsigned short)atoi(service)); // 设置服务器端口号
int ret=connect(sock, (struct sockaddr *)&sin, sizeof(sin)); // 连接到服务器,第二个参数指向存放服务器地址的结构,第三个参数为该结构的大小,返回值为0时表示无错误发生,
pthread_create(&thread_handle,NULL,client_recv,(void*)sock);
while(1){
printf("请输入要发送的消息:");
scanf("%s",buf);
if(strcmp(buf,"exit")==0){
printf("退出成功!\n");
break;
}
int c1=send(sock,buf,strlen(buf),0);
}

close(sock); // 关闭监听套接字

printf("按回车键继续...");
getchar(); // 等待任意按键
}

服务器端程序:

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
/* server2.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#define max_socks 10000
#define buf_size 1000
int* socks[max_socks],now_loc;
void* serve(void* in){
int my_loc=(long)in;
int my_sock=*(socks[my_loc]);
char* pts=new char[buf_size+1];
while(1){
int c1 = recv(my_sock, pts, buf_size, 0);
if(c1==0){
close(my_sock);
socks[my_loc]=NULL;
break;
}
pts[c1]='\0';
printf("服务器收到消息: %s\n",pts);
for(int i=0;i<max_socks;i++){
if(socks[i]!=NULL){
send(*socks[i], pts, strlen(pts), 0);
}
}
}
return NULL;
}

int main(int argc, char *argv[])
/* argc: 命令行参数个数, 例如:C:\> TCPServer 8080
argc=2 argv[0]="TCPServer",argv[1]="8080" */
{
pthread_t* thread_handles;
thread_handles=(pthread_t*)malloc(max_socks*sizeof(pthread_t));
struct sockaddr_in fsin; /* the from address of a client */
int msock, ssock; /* master & slave sockets */
char *service = "50500";
struct sockaddr_in sin; /* an Internet endpoint address */
int alen; /* from-address length */
char pts[1000]; /* pointer to time string */
time_t now; /* current time */

msock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); // 创建套接字,参数:因特网协议簇(family),流套接字,TCP协议
// 返回:要监听套接字的描述符或INVALID_SOCKET

memset(&sin, '\0', sizeof(sin)); // 从&sin开始的长度为sizeof(sin)的内存清0
sin.sin_family = AF_INET; // 因特网地址簇(INET-Internet)
sin.sin_addr.s_addr = INADDR_ANY; // 监听所有(接口的)IP地址。
sin.sin_port = htons((unsigned short)atoi(service)); // 监听的端口号。atoi--把ascii转化为int,htons--主机序到网络序(host to network,s-short 16位)
bind(msock, (struct sockaddr *)&sin, sizeof(sin)); // 绑定监听的IP地址和端口号

listen(msock, 5); // 建立长度为5的连接请求队列,并把到来的连接请求加入队列等待处理。

while (1)
{ // 检测是否有按键,如果没有则进入循环体执行
alen = sizeof(struct sockaddr); // 取到地址结构的长度
ssock = accept(msock, (struct sockaddr *)&fsin, (socklen_t *)&alen); // 如果在连接请求队列中有连接请求,
socks[now_loc]=new int(ssock);
pthread_create(&thread_handles[now_loc],NULL,serve,(void*)now_loc);
now_loc++;
}
close(msock); // 关闭监听套接字
}

运行效果:
image.png

image.png

2.服务器程序转发某个客户端发来的消息时都在消息前面加上该客户端的IP地址和端口号以及服务器的当前时间。要求服务器程序把转发的消息也显示出来。

服务器程序(修改部分):

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
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#define max_socks 10000
#define buf_size 1000
int* socks[max_socks],now_loc;
struct sockaddr_in fsin[max_socks]; /* the from address of a client */
void* serve(void* in){
int my_loc=(long)in;
int my_sock=*(socks[my_loc]);
char* pts=new char[buf_size+1];
while(1){
time_t t = time(NULL);
char ch[64] = {0};
char result[100] = {0};
unsigned int tem=fsin[my_loc].sin_addr.s_addr;
strftime(ch, sizeof(ch) - 1, "%Y-%m-%d--%H:%M:%S", localtime(&t));

int c1 = sprintf(pts, "\nip: %d.%d.%d.%d port: %u\ntime: %s\nmessage: ",
(tem<<24)>>24,(tem<<16)>>24,(tem<<8)>>24,tem>>24,fsin[my_loc].sin_port,ch);

int c2 = recv(my_sock, pts + c1, 1000-c1, 0);
pts[c1 + c2] = '\n';
pts[c1+c2+1]='\0';
if(c2==0){
close(my_sock);
socks[my_loc]=NULL;
break;
}
printf("%s\n",pts);
for(int i=0;i<max_socks;i++){
if(socks[i]!=NULL){
send(*socks[i], pts, strlen(pts), 0);
}
}
}
return NULL;
}

int main(int argc, char *argv[])
/* argc: 命令行参数个数, 例如:C:\> TCPServer 8080
argc=2 argv[0]="TCPServer",argv[1]="8080" */
{
pthread_t* thread_handles;
thread_handles=(pthread_t*)malloc(max_socks*sizeof(pthread_t));
int msock, ssock; /* master & slave sockets */
char *service = "50500";
struct sockaddr_in sin; /* an Internet endpoint address */
int alen; /* from-address length */
char pts[1000]; /* pointer to time string */
time_t now; /* current time */

msock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); // 创建套接字,参数:因特网协议簇(family),流套接字,TCP协议
// 返回:要监听套接字的描述符或INVALID_SOCKET

memset(&sin, '\0', sizeof(sin)); // 从&sin开始的长度为sizeof(sin)的内存清0
sin.sin_family = AF_INET; // 因特网地址簇(INET-Internet)
sin.sin_addr.s_addr = INADDR_ANY; // 监听所有(接口的)IP地址。
sin.sin_port = htons((unsigned short)atoi(service)); // 监听的端口号。atoi--把ascii转化为int,htons--主机序到网络序(host to network,s-short 16位)
bind(msock, (struct sockaddr *)&sin, sizeof(sin)); // 绑定监听的IP地址和端口号

listen(msock, 5); // 建立长度为5的连接请求队列,并把到来的连接请求加入队列等待处理。

while (1)
{ // 检测是否有按键,如果没有则进入循环体执行
alen = sizeof(struct sockaddr); // 取到地址结构的长度
ssock = accept(msock, (struct sockaddr *)&fsin[now_loc], (socklen_t *)&alen); // 如果在连接请求队列中有连接请求,
socks[now_loc]=new int(ssock);
pthread_create(&thread_handles[now_loc],NULL,serve,(void*)now_loc);
now_loc++;
}
close(msock); // 关闭监听套接字
}

运行截屏:
image.png

image.png

3.新客户刚连接时服务器端把“enter”消息(包含客户端IP地址和端口号)发送给所有客户端。
服务器程序(修改部分):

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
//只修改了serve函数
void* serve(void* in){
int my_loc=(long)in;
int my_sock=*(socks[my_loc]);
char pts[buf_size+1];
char tem[buf_size+1]="Enter!\n";
while(1){
time_t t = time(NULL);
char ch[64] = {0};
char result[100] = {0};
unsigned int port=fsin[my_loc].sin_addr.s_addr;
strftime(ch, sizeof(ch) - 1, "%Y-%m-%d--%H:%M:%S", localtime(&t));

int c1 = sprintf(pts, "\nip: %d.%d.%d.%d port: %u\ntime: %s\nmessage: %s",
(port<<24)>>24,(port<<16)>>24,(port<<8)>>24,port>>24,fsin[my_loc].sin_port,ch,tem);
printf("%s\n",pts);
for(int i=0;i<max_socks;i++){
if(socks[i]!=NULL){
send(*socks[i], pts, strlen(pts), 0);
}
}
int c2 = recv(my_sock,tem,buf_size, 0);
tem[c2] = '\0';
if(c2==0){
close(my_sock);
socks[my_loc]=NULL;
break;
}
}
return NULL;
}

运行截屏:
第一个客户端启动:
image.png
第二个客户端启动:
image.png

4.客户端输入exit时退出客户端程序(正常退出),或者客户端直接关闭窗口退出(异常退出),服务器都会把该客户leave的消息广播给所有客户。
服务器程序(修改部分):
只修改了serve函数:

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
void* serve(void* in){
int my_loc=(long)in;
int my_sock=*(socks[my_loc]);
char pts[buf_size+1];
char tem[buf_size+1]="Enter!\n";
while(1){
time_t t = time(NULL);
char ch[64] = {0};
char result[100] = {0};
unsigned int port=fsin[my_loc].sin_addr.s_addr;
strftime(ch, sizeof(ch) - 1, "%Y-%m-%d--%H:%M:%S", localtime(&t));

int c1 = sprintf(pts, "\nip: %d.%d.%d.%d port: %u\ntime: %s\nmessage: %s",
(port<<24)>>24,(port<<16)>>24,(port<<8)>>24,port>>24,fsin[my_loc].sin_port,ch,tem);
printf("%s\n",pts);
for(int i=0;i<max_socks;i++){
if(socks[i]!=NULL){
send(*socks[i], pts, strlen(pts), 0);
}
}
if(strcmp(tem,"Leave!\n")==0){
close(my_sock);
socks[my_loc]=NULL;
break;
}
int c2 = recv(my_sock,tem,buf_size, 0);
tem[c2] = '\0';
if(c2==0){
sprintf(tem,"Leave!\n");
}
}
return NULL;
}

运行截屏:
客户端用输入exit离开时:
image.png
客户端用Ctrl+C退出时:
image.png

5.运行客户端程序测试与老师的服务器程序的连接(103.26.79.35:50500)。
运行截屏(客户端):

image.png

实验体会

还是挺有趣的…