1.服务器
1.服务器采用了线程进行编写,这样可以共享链表,比较简单。
2.实现思路:由客户端发送消息(内容包含:客户端自定义的名字、客户端发送消息的类别、客户端发送的消息内容),服务器接收到消息后根据消息的类别做出一下反应。
1.消息类别:登录。服务器将接收到的消息显示在服务器上,并且遍历链表发送给除改客户端的其他客户端。然后创建一个节点保存客户端的IP和端口号。
2.消息类别:发送消息。服务器将接收到的消息显示在服务器上,并且遍历链表发送给除改客户端的其他客户端。
3.消息类别:下线。服务器将接收到的消息显示在服务器上,并且遍历链表发送给除改客户端的其他客户端。当遍历到改客户端时,删除改节点。
3.当服务器要发送消息时,在子线程里面接收来自终端上的消息,遍历链表发送给所有客户端。
/*===============================================
* 文件名称:server.c
* 创 建 者:懒
* 创建日期:2022年10月09日
* 描 述:
================================================*/
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <pthread.h>
typedef struct linklist{//创建存储客户端ip、端口号的链表
struct sockaddr_in addr;
struct linklist* next;
}l_node, *l_pnode;
typedef struct sockt_data{//创建收发的信息结构体
char name[64];//名字
char type[8];//消息类别
char text[128];//信息内容
}data_t;
typedef struct Sock{//创建存储服务器发送数据的信息结构体
struct sockaddr_in caddr;
data_t cdata;//发送消息结构体
int sockfd;//套接字
}sock_t;
l_pnode create();//创建链表节点
void login(int sockfd,data_t data,struct sockaddr_in saddr);//登录
void line(int sockfd,data_t data,struct sockaddr_in saddr);//发送和下线
void *func(void *arg);//子线程
l_pnode S;//开辟一个全局变量的链表
int main(int argc, char *argv[])
{
/**************** 1、创建套接字 -- socket *********************/
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
/****************2、绑定IP地址和端口号 -- bind ****************/
struct sockaddr_in saddr, caddr;
caddr.sin_family = AF_INET;
caddr.sin_port = htons(8888);//将主机字节序转换为网络字节序再赋值
caddr.sin_addr.s_addr = htons(INADDR_ANY);
int s_len = sizeof(saddr);
bind(sockfd, (struct sockaddr*)&caddr, s_len);
/**************** 3、发送接送数据 *****************************/
data_t sdata={0,0,0};//定义接送数据结构体
int d_len=sizeof(sdata);
int c_len = sizeof(caddr);//计算服务器地址结构的大小
memset(&saddr, 0,c_len);//清空接收地址结构
S = (l_pnode)malloc(sizeof(l_node));//初始化链表
S->next=NULL;
sock_t csock;//创建子线程所需结构体
csock.sockfd=sockfd;//传递套接字
pthread_t thread;
pthread_create(&thread,NULL,func,&csock); //创建子线程
while(1)
{
memset(&sdata, 0, d_len);//清空接送到的数据
recvfrom(sockfd, &sdata, d_len, 0, (struct sockaddr*)&saddr, &c_len);//接送来自客户端的消息
printf("%s:%s\n",sdata.name,sdata.text);//在服务器中显示接送到的消息
if(strcmp(sdata.type,"login")==0)//判断接受消息内型
login(sockfd,sdata,saddr);//上线操作
else
line(sockfd,sdata,saddr);//聊天、下线操作
}
/**************** 4、关闭套接字 *******************************/
close(sockfd);
return 0;
}
/**************** 子线程 **************************************/
void *func(void *arg) //线程处理函数
{
sock_t csock=*(sock_t *)arg;//获取主函数中传递过来的内容
strcpy(csock.cdata.type,"login");//设置要发送的结构图
strcpy(csock.cdata.name,"服务器");
while(1)
{
memset(&csock.cdata.text, 0,128);//清空输入进来的内容
fgets(csock.cdata.text,64,stdin);//获取输入进来的内容
csock.cdata.text[strlen(csock.cdata.text)-1] = '\0';//删除获取进来的回车'\n'
line(csock.sockfd,csock.cdata,csock.caddr);//向所有在线的客户端发送入进来的内容
}
}
/**************** 上线 ***************************************/
void login(int sockfd,data_t data,struct sockaddr_in saddr)//登录
{
int d_len = sizeof(data);
int s_len = sizeof(saddr);
l_pnode new = create();//创建新节点
new->addr = saddr;//传递接送到的客户端IP、端口号
new->next = NULL;
l_pnode N = create();
N=S;
while(N->next)//尾插并发送给其他客户端
{
N = N->next;
sendto(sockfd, &data, d_len, 0, (struct sockaddr*)&N->addr, s_len);//发送给除该客户信息的其他客户
}
N->next = new;
}
/**************** 聊天、下线 *******************************/
void line(int sockfd,data_t data,struct sockaddr_in saddr)//聊天、下线
{
int d_len = sizeof(data);
int s_len = sizeof(saddr);
l_pnode N = create();
N=S;
while(N->next)
{
if((N->next->addr.sin_addr.s_addr==saddr.sin_addr.s_addr)
&& (N->next->addr.sin_port==saddr.sin_port))//判断是否存在该客户端数据
{
if(strcmp(data.type,"line")==0)//下线,删除用户信息
{
l_pnode Q = N->next;
N->next = Q->next;//连线
free(Q);//释放空间
Q = NULL;
}
if(strcmp(data.type,"send_t")==0)//聊天,跳过用户
N = N->next;
}
if(N->next != NULL)//判断是否到尾节点
{
N = N->next;
sendto(sockfd, &data, d_len, 0, (struct sockaddr*)&N->addr, s_len);//发送给除该客户信息的其他客户
}
else
break;
}
}
/**************** 创建链表新节点 ******************************/
l_pnode create()
{
l_pnode S = (l_pnode)malloc(sizeof(l_node));
S->next = NULL;
return S;
}
2.客户端
1.客户端采用了进程进行编写,由子进程接收来自终端的消息发送出去,由父进程来接收消息。当运行程序时。
1.要求需要输入用户名,输入用户名后会向服务器发送(用户名、消息类别:登录、内容:上线了)。
2.客户端发送消息(用户名、消息类别:聊天、内容:消息内容)。
2.客户端下线,需要用户输入“line!!!”(用户名、消息类别:下线、内容:下线了)。接受程序。
2.当接受来自服务器的消息,将消息里的名字和内容打印出来
/*===============================================
* 文件名称:client.c
* 创 建 者:懒
* 创建日期:2022年10月09日
* 描 述:
================================================*/
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
typedef struct sockt_data{//自定义结构体用于存放发送、接送到的信息
char name[64];
char type[8];
char text[128];
}data_t;
void mysignal(int arg)//回收子进程
{
wait(NULL);
exit(0);
}
void login(int sockfd,data_t cdata,struct sockaddr_in caddr);//登录
void send_t(int sockfd,data_t data,struct sockaddr_in saddr);//发送信息
void line(int sockfd,data_t cdata,struct sockaddr_in caddr);//下线
int main(int argc, char *argv[])
{
if(argc!=2)
printf("请正确输入: ./client IP号\n");
/**************** 1、创建套接字 -- socket *********************/
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
/****************2、绑定IP地址和端口号 -- bind ****************/
struct sockaddr_in saddr, caddr;
caddr.sin_family = AF_INET;
caddr.sin_port = htons(8888);//将主机字节序转换为网络字节序再赋值
caddr.sin_addr.s_addr = inet_addr(argv[1]);
int s_len = sizeof(saddr);
/**************** 3、发送接送数据 *****************************/
data_t cdata={0,0,0},sdata={0,0,0};//定义收发数据结构体
int d_len=sizeof(cdata);//计算数据结构体长度
memset(&saddr, 0,s_len);//清空接收地址结构
memset(&cdata, 0,d_len);//清空接收地址结构
signal(SIGCHLD, mysignal);//捕获子进程释放信号
pid_t pid=fork();//创建线程
if(0==pid)//子线程
{
printf("请输入你的用户名--->");
fgets(cdata.name,64,stdin);//获取输入进来的名字
cdata.name[strlen(cdata.name)-1] = '\0';//删除获取进来的回车'\n'
login(sockfd,cdata,caddr);//登录
while(1)
{
fgets(cdata.text,64,stdin);//获取输入进来的内容
cdata.text[strlen(cdata.text)-1] = '\0';//删除获取进来的回车'\n'
if(strcmp(cdata.text,"line!!!")==0) //判断是否结束聊天
line(sockfd,cdata,caddr);//下线
send_t(sockfd,cdata,caddr);//聊天
}
}
while(1)
{
memset(&sdata, 0, d_len);//清空接送到的数据
recvfrom(sockfd, &sdata, d_len, 0, (struct sockaddr*)&saddr, &s_len);
if(strcmp(sdata.type,"send_t")==0)//在客户端中显示接送到的消息
printf("%s:%s\n",sdata.name,sdata.text);
else
printf("%s-->%s\n",sdata.name,sdata.text);
}
/**************** 4、关闭套接字 *******************************/
close(sockfd);
return 0;
}
/**************** 登录 ***************************************/
void login(int sockfd,data_t cdata,struct sockaddr_in caddr)//登录
{
int d_len = sizeof(cdata);
int s_len = sizeof(caddr);
strcpy(cdata.type,"login");
strcpy(cdata.text,"上线了");
sendto(sockfd, &cdata, d_len, 0, (struct sockaddr*)&caddr, s_len);
}
/**************** 下线 ***************************************/
void line(int sockfd,data_t cdata,struct sockaddr_in caddr)//下线
{
int d_len = sizeof(cdata);
int s_len = sizeof(caddr);
strcpy(cdata.type,"line");
strcpy(cdata.text,"下线了");
sendto(sockfd, &cdata, d_len, 0, (struct sockaddr*)&caddr, s_len);
exit(0);
}
/**************** 聊天 ***************************************/
void send_t(int sockfd,data_t cdata,struct sockaddr_in caddr)//发送信息
{
int ret=0;
int d_len = sizeof(cdata);
int s_len = sizeof(caddr);
strcpy(cdata.type,"send_t");
ret = sendto(sockfd, &cdata, d_len, 0, (struct sockaddr*)&caddr, s_len);
}