简要说明
实现多线程聊天,具体功能包括用户加入、发言及离开
用户上限小于100
先运行服务器端 后运行客户端进行连接
每当有客户端加入时,每个客户端都将展示当前加入的用户和当前用户数
输入消息开头为leave信息则默认当前用户退出,其它客户端将展示当前退出用户和当前用户数;服务器端将展示当前所有用户,并提示退出用户信息
每个客户端发出的信息所有其它客户端都将接收
(当时做的时候参考了不少材料 代码几乎全部注释了 仔细看应该很容易能看懂)
服务器端实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <pthread.h>
const unsigned short LOCALPORT = 8888;
const char *LOCALIP = "127.0.0.1";
char SendBuffer[5120];
char RecvBuffer[5120];
struct ExchMsg {
int Socket; //套接字
ushort Alarm;//当前状态
ushort CurrNum;//在线人数
};
pthread_t RecvThread;//声明接收线程id
pthread_t SendThread;//声明发送线程id
int ClientSocArr[100];//存放用户名(套接字名)
struct ExchMsg *OutputMsg, *InputMsg;//输出和接收的信息 声明
int CurrNum = 0;//当前无人在线
void SocCount(int DealSoc) {
//统计当前用户(存在的套接字)
for (int i = 0; i < (CurrNum + 1); i++) {
if (ClientSocArr[i] == DealSoc) {
for (; i < CurrNum; i++) {
ClientSocArr[i] = ClientSocArr[i + 1];
}
//打印当前在线所有用户
printf("——————————————————————————\n");
printf("NOW We Have:\n");
printf("——————————————————————————\n");
for (int j = 0; j < CurrNum; j++) {
if (j != (CurrNum - 1)) {
printf("User%d ", ClientSocArr[j]);
}
else {
printf("User%d\n", ClientSocArr[j]);
printf("——————————————————————————\n");
}
}
}
}
}
void SendToClient(char *buffer, int DealSoc) {
// 发送信息给客户端的线程
for (int j = 0; j < CurrNum; j++) {
if (ClientSocArr[j] == DealSoc) {
//若为当前处理的套接字 则继续
continue;
}
if (send(ClientSocArr[j], buffer, 5120, 0) == -1) {
//如果未能发送成功
fprintf(stderr, "%s\n", strerror(errno));//输出错误提示
}
}
bzero(buffer, 5120);//缓存区置零
}
void *RecvFromClient(void *recvsoc) {
//从客户端接收消息的线程
int DealSoc = *(int *)recvsoc;
while (1) {
if (recv(DealSoc, RecvBuffer, 5120, 0) == -1) {
//未能接收成功
fprintf(stderr, "Receive Message Error: %s\n", strerror(errno));//打印错误提示
}
InputMsg = (struct ExchMsg *)RecvBuffer;//从接收缓存提取信息
InputMsg->Socket = DealSoc;//套接字置为当前处理的套接字
if (InputMsg->Alarm == 1) {//当用户离开情况发生
CurrNum--;
InputMsg->CurrNum = CurrNum;//统计数量减一
SocCount(DealSoc);//将离开的套接字从当前数组移除,统计并打印当前套接字
printf("User %d has left the room\n", DealSoc);
SendToClient(RecvBuffer, DealSoc); // 将此用户的消息发给其他用户
close(DealSoc);//关闭当前处理套接字
return NULL;
}
SendToClient(RecvBuffer, DealSoc); // 将此用户的消息发给其他用户
}
close(DealSoc);
return NULL;
}
int main(int argc, char const *argv[])
{
int serversoc, recvsoc;
socklen_t sockleng;
struct sockaddr_in serveraddr, clientaddr;
int perrno;
if ((serversoc = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
fprintf(stderr, "ERROR IN Creating server socket, %s\n", strerror(errno));
exit(0);//创建服务器套接字出错 提示错误信息 正常退出
}
serveraddr.sin_family = AF_INET;// 服务器端地址信息/协议簇为tcpip协议
serveraddr.sin_port = htons(LOCALPORT);//保存端口号
serveraddr.sin_addr.s_addr = inet_addr(LOCALIP);//ip地址信息 将LOCALIP转换为长整型
if (bind(serversoc, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr)) == -1) {
fprintf(stderr, "ERROR IN Binding socket, %s\n", strerror(errno));
exit(0);//绑定套接字出错 提示错误信息 正常退出
}
if (listen(serversoc, 100) == -1) {
fprintf(stderr, "ERROR IN Listening socket, %s\n", strerror(errno));//监听套接字出错 提示错误信息
}
//无异常情况下 欢迎界面
printf("\n——————Welcome to the CHATROOM——————\n");
printf("\nCurrent port is %d, waiting for people to enter.\n", LOCALPORT);
while (1) {
if ((recvsoc = accept(serversoc, (struct sockaddr *)&clientaddr, &sockleng)) == 0) {
fprintf(stderr, "Connection wrong, %s\n", strerror(errno));//提示错误信息
continue;//连接出错
}
ClientSocArr[CurrNum++] = recvsoc;// 将该套接字保存进数组
printf("USER %d has shown up\n", recvsoc);//展示该用户已进入房间
OutputMsg = (struct ExchMsg *)SendBuffer;//将发送缓存区数据存放入outputmsg
OutputMsg->Socket = recvsoc;
OutputMsg->Alarm = 0;//表示为有用户进入聊天室
OutputMsg->CurrNum = CurrNum;
for (int j = 0; j < CurrNum; j++) {
// 当有用户加入时群发通知给所有当前用户
if (send(ClientSocArr[j], SendBuffer, 5120, 0) == -1) {
fprintf(stderr, "%s\n", strerror(errno));//如果发送出错 打印错误提示
}
}
bzero(SendBuffer, 5120);//发送缓存置零
if ((perrno = pthread_create(&RecvThread, NULL, RecvFromClient, &recvsoc)) != 0) {
// 创建子用户消息接收处理的线程
fprintf(stderr, "Failed to create receive user message processing thread, %s\n", strerror(perrno));
exit(perrno);//若创建失败 打印错误提示
}
}
close(serversoc);
return 0;
}
客户端实现
//client
//2020unix大作业+918106840611
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
extern int errno;
#define SERVER_PORT 8888
const char *SERVERIP = "127.0.0.1";
char SendBuffer[5120];
char RecvBuffer[5120];
struct ExchMsg {
int Socket;//套接字
ushort Alarm;//当前状态
ushort CurrNum;//当前人数
};
int clisocket;//客户端套接字声明
struct ExchMsg *OutputMsg, *InputMsg;//输出和接收的信息 声明
void *sendMsg(void *msg) {
//发送消息
while (1) {
OutputMsg = (struct ExchMsg *)SendBuffer;//缓存区信息转换为左述类型赋给outputmsg
OutputMsg->Socket = clisocket;
fgets(SendBuffer + sizeof(struct ExchMsg), 5120 - sizeof(struct ExchMsg), stdin);
//从标准输入读取一行并存储到缓冲区中
if (strncmp(SendBuffer + sizeof(struct ExchMsg), "leave", 5) == 0) {
//探查用户输入是否为leave,若是则视为用户选择离开
OutputMsg->Alarm = 1;//警示值标为1
if (send(clisocket, SendBuffer, 5120, 0) == -1) {//如果发送失败
fprintf(stderr, "%s\n", strerror(errno));//errno对应的错误提示字符串
}
close(clisocket);//关闭套接字
exit(0);//正常状态退出
}
else {
OutputMsg->Alarm = 2;// 否则认为用户想发送数据 将警示值标记为2
}
if (send(clisocket, SendBuffer, 5120, 0) == -1) {
fprintf(stderr, "%s\n", strerror(errno));//如果此时发送失败 输出错误提示
}
bzero(SendBuffer, 5120);//将缓冲区置零
}
return NULL;
}
int main(int argc, char const *argv[])
{
ssize_t sendLen;//被执行数据块(发送消息)大小
struct sockaddr_in seraddr, recvaddr;//地址
pthread_t Psend;//声明线程id
if ((clisocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {// 创建一客户端套接字 ip地址类型ipv4 套接字类型面向连接
fprintf(stderr, "%s\n", strerror(errno));//如果失败 输出错误提示
exit(errno);//错误退出
}
bzero(&seraddr, sizeof(struct sockaddr_in));//输出地址清空
// 服务器端地址信息
seraddr.sin_family = AF_INET;//协议簇为tcpip协议
seraddr.sin_addr.s_addr = inet_addr(SERVERIP);//ip地址信息 将SERVERIP转换为长整型
seraddr.sin_port = htons(SERVER_PORT);//保存端口号
if (connect(clisocket, (struct sockaddr *)&seraddr, sizeof(struct sockaddr)) == -1) {
// 请求连接服务器进程
fprintf(stderr, "请求连接服务器失败, %s\n", strerror(errno));//连接失败 输出提示
exit(errno);//错误退出
}
printf("——Connection to server succeeded——\n");//连接成功
printf("Current Ip&Port_%s:%d\n", inet_ntoa(seraddr.sin_addr), ntohs(seraddr.sin_port));//当前ip地址和端口号
pthread_create(&Psend, NULL, sendMsg, NULL);//新线程创建 用于消息发送
while (1) {
//接收消息
bzero(RecvBuffer, 5120);//缓冲区置零
if (recv(clisocket, RecvBuffer, 5120, 0) == -1) {
fprintf(stderr, "%s\n", strerror(errno));//当接收消息失败 输出错误信息
}
InputMsg = (struct ExchMsg *)RecvBuffer;
if (InputMsg->Alarm == 0) {//有用户进入聊天室
fprintf(stdout, " User%d Entered This ROOM,NOW We Have %d USERS \n", InputMsg->Socket, InputMsg->CurrNum);
}
else if (InputMsg->Alarm == 1) {//有用户离开聊天室
printf(" User%d Left This ROOM,NOW We Have %d USERS \n", InputMsg->Socket, InputMsg->CurrNum);
}
else if (InputMsg->Alarm == 2) {//其他用户发送消息
fprintf(stdout, "USER %d Said>> %s\n", InputMsg->Socket, RecvBuffer + sizeof(struct ExchMsg));
}
}
return 0;
}
/*发送失败时errno有如下可能
EACCES:不许对目标套接字文件写,或者路径前驱的一个目录节点不可搜索
EAGAIN,EWOULDBLOCK: 套接字已标记为非阻塞,而发送操作被阻塞
EBADF:sock不是有效的描述词 ECONNRESET:连接被用户重置
EDESTADDRREQ:套接字不处于连接模式,没有指定对端地址
EFAULT:内存空间访问出错 EINTR:操作被信号中断 EINVAL:参数无效
EISCONN:基于连接的套接字已被连接上,同时指定接收对象
EMSGSIZE:消息太大 ENOMEM:内存不足 ENOTCONN:套接字尚未连接,目标没有给出
ENOTSOCK:sock索引的不是套接字 EPIPE:本地连接已关闭*/
配置环境
VMware-ubuntu虚拟机64位
测试图片
server:
client1:
client2:
有错欢迎探讨指正~