【目标实现】
模拟一个聊天室,任意一个客户端窗口可以发送消息,同时也可以接收聊天室内所有人的消息。
【服务器端】
#include <stdio.h>
#include <cstring>
#include <algorithm>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
using namespace std;
int cli_socks[100], num = 0, judge = 0;
pthread_mutex_t mutex;
void *t_main(void *arg)
{
char str[100];
int sock = *(int *)arg;
while(1)
{
int len = read(sock, str, 100);
if(!len) break;
str[len] = 0;
pthread_mutex_lock(&mutex);
//向所有客户端发送消息
for(int i = 1; i <= num; i++)
{
write(cli_socks[i], str, sizeof(str));
}
pthread_mutex_unlock(&mutex);
}
int i;
//线程同步
pthread_mutex_lock(&mutex);
for(i = 1; i <= num; i++)
{
if(cli_socks[i] == sock)
break;
}
while(i < num)
{
cli_socks[i] = cli_socks[i+1];
i++;
}
num--;
pthread_mutex_unlock(&mutex);
printf("close connect is %d\n", sock);
close(sock);
if(!num && judge) exit(0);
}
int main(int argc, char ** argv)
{
int ser_sock, cli_sock;
sockaddr_in ser_addr, cli_addr;
ser_sock = socket(PF_INET, SOCK_STREAM, 0);//创建套接字
if(ser_sock == -1)
puts("socket error");
//地址再分配
int opt = 1;
setsockopt(ser_sock, SOL_SOCKET, SO_REUSEADDR, &opt, 4);
memset(&ser_addr, 0, sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ser_addr.sin_port = htons(atoi("9190"));
//分配地址信息
bind(ser_sock, (sockaddr *)&ser_addr, sizeof(ser_addr));
//转化可接受连接状态
listen(ser_sock, 5);
pthread_mutex_init(&mutex, NULL);//创建互斥量
pthread_t id;
puts("等待进入...");
while(1)
{
socklen_t cli_len = sizeof(cli_addr);
//请求受理
cli_sock = accept(ser_sock, (sockaddr *)&cli_addr, &cli_len);
judge = 1;
//利用互斥量锁住临界区
pthread_mutex_lock(&mutex);
cli_socks[++num] = cli_sock;
pthread_mutex_unlock(&mutex);
pthread_create(&id, NULL, t_main, (void *)&cli_sock);//创建线程
pthread_detach(id);//线程运行结束自动释放所有资源
printf("connect is %d\n", cli_sock);
}
pthread_mutex_destroy(&mutex);//销毁互斥量
close(ser_sock);
return 0;
}
【客户端】
#include <stdio.h>
#include <unistd.h>
#include <cstring>
#include <algorithm>
#include <arpa/inet.h>
#include <pthread.h>
#include <iostream>
using namespace std;
char name[100], na[100];
void *wr(void *arg)
{
char s[100], mes[100];
int sock = *(int *)arg;
int x = sizeof(s);
while(1)
{
fgets(s, sizeof(s), stdin);
s[strlen(s) - 1] = 0;
if(!strcmp(s, "q")) break;
sprintf(mes, "%s %s", name, s);
write(sock, mes, strlen(mes));
}
sprintf(mes, "【系统消息】%s退出聊天室", na);
write(sock, mes, strlen(mes));
close(sock);
exit(0);
}
void *rd(void *arg)
{
int sock = *(int *)arg;
char s[100];
while(1)
{
int len = read(sock, s, 100);
s[len] = 0;
puts(s);
}
}
int main(int argc, char **argv)
{
puts("请输入用户名!");
char mes[50];
cin >> na;
getchar();//防止fgets读回车
int sock = socket(PF_INET, SOCK_STREAM, 0);//创建套接字
sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(atoi("9190"));
sprintf(name, "[%s]", na);
connect(sock, (sockaddr *)&addr, sizeof(addr));//请求连接
sprintf(mes, "【系统消息】%s进入聊天室", na);
write(sock, mes, strlen(mes));
pthread_t id_rd, id_wr;
//创建读写线程
pthread_create(&id_wr, NULL, wr, (void *)&sock);
pthread_create(&id_rd, NULL, rd, (void *)&sock);
pthread_join(id_wr, NULL);//等待写线程终止
//pthread_join(id_rd, NULL);
close(sock);
return 0;
}
【效果截图】
【发现问题】
1.exit和return的区别:传送门
2.linux用gets会出现警告,由于他没有指定输入字符的大小,如果输入字符大于定义的数组长度的时候,那么就会发生内存越界问题。 而用fgets函数则可以根据定义数组的长度自动截断字符,而消除一些安全隐患。 但是fgets会读入最后的回车。
3.客户端代码的21行是把数据送到输出缓冲,34行是从输入缓冲读取,不用担心两个会同时进行,因为并不在一个通道上。
4.全局变量num和cli_socks所在的代码行构成临界区,因为如果不同的线程同时运行可能会引发错误。