服务器
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"line:%d",__LINE__);\
perror(msg);\
}while(0)
#define PORT 8888
#define IP "192.168.31.244" //任意IP,会将本机的所有ip绑定到套接字上
#define N 128
//通讯结构体
typedef struct
{
char type; //保存协议
char name[20]; //保存姓名
char text[N]; //保存信息内容
}MSG;
//链表节点
typedef struct node
{
struct sockaddr_in cin;
struct node* next;
}linklist;
int do_recv(int sfd, linklist* head);
int do_login(int sfd, MSG recv_msg, struct sockaddr_in cin, linklist* head);
int do_quit(int sfd, MSG recv_msg, struct sockaddr_in cin, linklist* head);
int do_chat(int sfd, MSG recv_msg, struct sockaddr_in cin, linklist* head);
int do_system(int sfd, struct sockaddr_in cin);
int main(int argc, const char *argv[])
{
//创建套接字
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
//允许本地端口快速重用
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))<0)
{
ERR_MSG("setsockopt");
return -1;
}
//绑定服务器的ip地址和端口号
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.s_addr = inet_addr(IP);
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin))<0)
{
ERR_MSG("bind");
return -1;
}
printf("服务器准备完毕\n");
//创建父子进程
pid_t pid = fork();
if(pid > 0)
{
//父进程
//创建存储客户端信息的链表
linklist* head = (linklist*)malloc(sizeof(linklist));
head->next = NULL;
//接收客户端的信息:协议+名字+文本信息结构体
do_recv(sfd, head);
}
else
{
//子进程
//发送系统消息:从终端获取消息,发送
do_system(sfd, sin);
}
return 0;
}
int do_system(int sfd, struct sockaddr_in sin)
{
MSG sys_msg = {'C', "**system**"};
while(1)
{
bzero(sys_msg.text, N);
fgets(sys_msg.text, N , stdin);
//将当前子进程当做客户端,父进程为服务器,发送信息
if(sendto(sfd, &sys_msg, sizeof(sys_msg), 0, (void*)&sin, sizeof(sin)) <0)
{
ERR_MSG("sendto");
exit(1);
}
}
printf("发送成功\n");
}
int do_recv(int sfd, linklist* head)
{
MSG recv_msg;
struct sockaddr_in cin;
socklen_t clen = sizeof(cin);
while(1)
{
memset(&recv_msg, 0, sizeof(recv_msg));
//接收客户端的请求
if(recvfrom(sfd, &recv_msg, sizeof(recv_msg), 0, (struct sockaddr*)&cin,&clen) < 0)
{
ERR_MSG("recvfrom");
return -1;
}
printf("type = %c __%d__\n", recv_msg.type, __LINE__);
switch(recv_msg.type)
{
case 'L':
//提取客户端的地址信息,并发送给其他客户端,然后将地址信息插入到链表中
do_login(sfd, recv_msg, cin, head);
break;
case 'C':
//遍历链表,将数据包转发出去,除了自己之外的客户端。
do_chat(sfd, recv_msg, cin, head);
break;
case 'Q':
//将退出信息发送给出自己以外的客户端,然后将自己从链表中删除
do_quit(sfd, recv_msg, cin, head);
break;
}
}
return 0;
}
int do_quit(int sfd, MSG recv_msg, struct sockaddr_in cin, linklist* head)
{
//重现拼接文本信息
printf("%s [%s : %d]下线\n", recv_msg.name, inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
sprintf(recv_msg.text, "-------%s 已下线-------", recv_msg.name);
//遍历链表
while(head->next != NULL)
{
if(memcmp(&cin, &head->next->cin, sizeof(cin)) == 0)
{
//删除head->next节点
linklist* temp = head->next;
head->next = temp->next;
free(temp);
}
else
{
head = head->next;
if(sendto(sfd, &recv_msg, sizeof(recv_msg), 0, (void*)&head->cin,sizeof(head->cin)) < 0)
{
ERR_MSG("sendto");
exit(1);
}
}
}
return 0;
}
int do_chat(int sfd, MSG recv_msg, struct sockaddr_in cin, linklist* head)
{
//重新拼接群聊消息:名字:信息
char buf[N] = "";
sprintf(buf, "%s:%s", recv_msg.name, recv_msg.text);
strcpy(recv_msg.text, buf);
//循环发送给除了自己以外的客户端
while(head->next != NULL)
{
head = head->next;
if(memcmp(&head->cin, &cin, sizeof(cin)) != 0)
{
if(sendto(sfd, &recv_msg, sizeof(recv_msg), 0, (void*)&head->cin,sizeof(head->cin)) < 0)
{
ERR_MSG("sendto");
exit(1);
}
}
}
return 0;
}
int do_login(int sfd, MSG recv_msg, struct sockaddr_in cin, linklist* head)
{
//打印客户端登录成功
printf("%s [%s : %d]登录成功\n", recv_msg.name, inet_ntoa(cin.sin_addr),
ntohs(cin.sin_port));
//拼接登录成功信息
sprintf(recv_msg.text, "-------%s 登录成功-------", recv_msg.name);
//遍历链表发送信息
while(head->next != NULL)
{
head = head->next;
if(sendto(sfd, &recv_msg, sizeof(recv_msg), 0, (struct sockaddr*)&head->cin, sizeof(head->cin)) < 0)
{
ERR_MSG("sendto");
exit(1);
}
}
//将登录成功的客户端地址信息添加到链表中
linklist* temp = (linklist*)malloc(sizeof(linklist));
temp->next = NULL;
temp->cin = cin;
//上述while循环结束后 head 就处于尾节点处,可以直接指向temp节点
head->next = temp;
return 0;
}
客户端
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"line:%d",__LINE__);\
perror(msg);\
}while(0)
#define PORT 8888
#define IP "192.168.31.244" //任意IP,会将本机的所有ip绑定到套接字上
#define N 128
typedef struct
{
char type; //保存协议,‘L’登录协议, ‘C’群聊协议 ‘Q’退出协议
char name[20];
char text[N];
}MSG;
typedef void (*sighandler_t)(int);
int do_recv(int sfd);
int do_chat(int sfd, struct sockaddr_in sin, MSG msg);
void handler(int sig)
{
while(waitpid(-1, NULL, WNOHANG) > 0);
exit(1);
}
int main(int argc, const char *argv[])
{
sighandler_t s = signal(SIGCHLD, handler);
if(s == SIG_ERR)
{
ERR_MSG("signal");
return -1;
}
//创建套接字
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
//绑定客户端的ip和端口,非必须的
//填充服务器的ip地址和端口号
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.s_addr = inet_addr(IP);
socklen_t slen = sizeof(sin);
//做登录请求
MSG send_msg;
send_msg.type = 'L';
printf("请输入登录名>>>");
fgets(send_msg.name, sizeof(send_msg.name), stdin);
send_msg.name[strlen(send_msg.name)-1] = 0;
//发送登录请求
if(sendto(sfd, &send_msg, sizeof(send_msg), 0, (struct sockaddr*)&sin,sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
pid_t pid = fork();
if(pid > 0)
{
//父进程做接收
do_recv(sfd);
}
else if(0 == pid)
{
//子进程发送信息;
do_chat(sfd, sin, send_msg);
}
else
{
ERR_MSG("fork");
return -1;
}
return 0;
}
int do_chat(int sfd, struct sockaddr_in sin, MSG msg)
{
while(1)
{
//从终端获取数据
bzero(msg.text, N);
fgets(msg.text, N, stdin);
msg.text[strlen(msg.text)-1] = 0;
//如果是聊天,则协议设置为C,如果是quit,则协议设置为Q,然后发送。
if(strncasecmp(msg.text, "quit", 4) == 0)
{
msg.type = 'Q';
}
else
{
msg.type = 'C';
}
if(sendto(sfd, &msg, sizeof(msg), 0, (void*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
exit(1);
}
//如果是退出协议,发送结束后,子进程退出
//父进程需要回收子进程资源
if(msg.type == 'Q')
{
exit(1);
}
}
return 0;
}
int do_recv(int sfd)
{
MSG recv_msg;
while(1)
{
memset(&recv_msg, 0, sizeof(recv_msg));
if(recvfrom(sfd, &recv_msg, sizeof(recv_msg), 0, NULL, NULL)<0)
{
ERR_MSG("recvfrom");
return -1;
}
printf("%s\n\n", recv_msg.text);
}
return 0;
}