目录
使用select实现TCP并发服务器
使用poll实现TCP客户端
UDP实现网络聊天室
服务器
ser.h
main.c
func_ser.c
makefile
客户端
cli.h
main.c
func_cli.c
makfile
思维导图
使用select实现TCP并发服务器
#include <myhead.h>
int main(int argc, const char *argv[])
{
//创建套接字
int sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd<0){
perror("socket");
return -1;
}
//设置端口快速重用
int reuse=1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0){
perror("setsockopt");
return -1;
}
printf("端口快速重用成功\n");
//绑定
//填充地址信息结构体
struct sockaddr_in sin;
sin.sin_family=AF_INET;
sin.sin_port=htons(6666);
sin.sin_addr.s_addr=inet_addr("192.168.125.13");
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0){
perror("bind");
return -1;
}
//listen
listen(sfd,128);
//接收客户端地址信息
struct sockaddr_in cin;
socklen_t addrlen=sizeof(cin);
//创建集合
fd_set readfds,tempfds;
//清空集合
FD_ZERO(&readfds);
//将要监听的文件描述符加入集合
FD_SET(0,&readfds);
FD_SET(sfd,&readfds);
int res; //接收select返回值
char buf[128]; //
int maxfd=sfd;
struct sockaddr_in cin_arr[1024]; //存储客户端地址信息
while(1){
//tempfds存储readfds,当事件触发时select
tempfds=readfds;
//select函数阻塞监听集合中的函数,直到有事件发生解除阻塞
res=select(maxfd+1,&tempfds,NULL,NULL,NULL);
if(0==res){
printf("time out\n");
return -1;
}
else if(res<0){
perror("select error");
return -1;
}
//运行到这说明有事件触发
for(int i=0;i<=maxfd;i++){
//如果i不在集合中直接遍历下一个
if(!FD_ISSET(i,&tempfds)){
continue;
}
//触发键盘输入事件
if(0==i){
printf("触发键盘输入事件:");
memset(buf,0,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]='\0';
printf("%s\n",buf);
}
//触发sfd事件
else if(sfd==i){
//accept
int newfd=accept(sfd,(struct sockaddr*)&cin,&addrlen);
if(newfd<0){
perror("accept");
return -1;
}
//终端打印客户端连接信息
printf("[%s %d] newfd=%d accept success\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);
//将连接的客户端加入集合
FD_SET(newfd,&readfds);
cin_arr[newfd]=cin;
//更新maxfd
maxfd=maxfd>newfd?maxfd:newfd;
}
//其他为客户端发送信息
else{
memset(buf,0,sizeof(buf));
//recv为0说明客户端下线
if(0==recv(i,buf,sizeof(buf),0)){
printf("[%s %d]客户端下线\n",inet_ntoa(cin_arr[i].sin_addr),ntohs(cin_arr[i].sin_port));
//下线需要关闭文件描述
close(i);
//客户端下线后需要再集合中函数newfd
FD_CLR(i,&readfds);
int j=0;
//如果移除的为最大的maxfd,则需要更新maxfd
if(i==maxfd){
//从maxfd开始往下遍历,找到集合中最大更新maxfd,break
for(j=maxfd;j>0;j--){
if(FD_ISSET(j,&readfds)){
maxfd=j;
break;
}
}
if(0==j)maxfd=0;
}
continue;
}
printf("[%s %d]%s\n",inet_ntoa(cin_arr[i].sin_addr),ntohs(cin_arr[i].sin_port),buf);
strcat(buf,"*-*");
send(i,buf,sizeof(buf),0);
}
}
}
close(sfd);
return 0;
}
使用poll实现TCP客户端
#include <myhead.h>
//使用poll实现TCP客户端
int main(int argc, const char *argv[])
{
//创建套接字
int cfd=socket(AF_INET,SOCK_STREAM,0);
if(cfd<0){
perror("socket");
return -1;
}
//绑定非必须
//填充服务器地址信息结构体
struct sockaddr_in sin;
sin.sin_family=AF_INET;
sin.sin_port=htons(6666);
sin.sin_addr.s_addr=inet_addr("192.168.125.13");
//创建集合
struct pollfd pollfds[2];
//监测0号文件描述符的读事件
pollfds[0].fd=0;
pollfds[0].events=POLLIN;
//监测cfd的读事件
pollfds[1].fd=cfd;
pollfds[1].events=POLLIN;
//连接服务器
if(connect(cfd,(struct sockaddr*)&sin,sizeof(sin))<0){
perror("connect");
return -1;
}
int res; //接收poll函数的返回值
char buf[128];
while(1){
//使用poll函数监测集合文件描述的事件
res=poll(pollfds,2,-1);
if(0==res){
printf("time out\n");
return -1;
}
else if(res<0){
perror("poll error");
return -1;
}
//运行到这说明有事件触发
if(pollfds[0].revents==POLLIN){
//从终端获取输入
memset(buf,0,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]='\0';
printf("触发键盘输入:%s\n",buf);
//发送给服务器
send(cfd,buf,sizeof(buf),0);
}
if(pollfds[1].revents=POLLIN){
memset(buf,0,sizeof(buf));
//接收服务器信息
recv(cfd,buf,sizeof(buf),0);
printf("收到服务器信息%s\n",buf);
}
}
//关闭文件描述符
close(cfd);
return 0;
}
UDP实现网络聊天室
服务器
ser.h
#ifndef __SER_H__
#define __SER_H__
#include <myhead.h>
#define ERR_MSG(msg) do{fprintf(stderr,"__%d__",__LINE__);\
perror(msg);\
}while(0)
#define IP "192.168.125.13"
#define PORT 6666
//通信结构体
typedef struct
{
int type; //用户状态:定义1为登录、2为聊天,3为客户端下线,4为系统信息
char name[128]; //用户名
char msg[128]; //消息
}user_t;
//线程传参结构体
typedef struct
{
sqlite3 *db;
int sfd;
}th_t;
//线程调用函数传参
typedef struct
{
th_t th;
user_t user;
}temp_t;
//在数据库中创建表
int create_sheet(sqlite3 *db);
//在数据库中插入表
int insert_sheet(sqlite3 *db,char *name,struct sockaddr_in cin);
//在数据库中查找
int search(sqlite3 *db,char *name,void *arg);
//查找回调函数
int select_callback(void *arg,int columns,char **column_text,char **colum_name);
//删除表中记录
int del_sheet(sqlite3 *db,char *name);
//删除表
int del_sheet_a(sqlite3 *db);
//服务发送系统信息线程
void *task(void *arg);
#endif
main.c
#include "ser.h"
//UDP实现网络聊天室
//UDP服务器
int main(int argc, const char *argv[])
{
int sfd; //socket文件描述符
//1.打开套接字
sfd=socket(AF_INET,SOCK_DGRAM,0);
if(sfd<0){
ERR_MSG("socket");
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);
//2.绑定
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0){
ERR_MSG("bind");
return -1;
}
printf("bind success\n");
//接收客户端地址信息
struct sockaddr_in cin;
socklen_t addrlen=sizeof(cin);
//创建数据库
sqlite3 *db; //数据库指针
//指向数据库的指针,数据库操作的句柄
if(sqlite3_open("./chat.db",&db)!=0){
fprintf(stderr,"__%d__sqlite3_open error",__LINE__);
return -1;
}
//分支线程传参
th_t th;
th.db=db;
th.sfd=sfd;
//创建分支线程,用于服务器给客户端发送系统信息
pthread_t tid;
if(pthread_create(&tid,NULL,task,&th)){
fprintf(stderr,"__%d__pthread_create error\n",__LINE__);
return -1;
}
//每次重启服务器后清空数据库表
del_sheet_a(db);
//创建表
create_sheet(db);
//search函数传参
temp_t temp;
temp.th.sfd=sfd;
user_t user; //消息结构体变量
int res; //登录时用户名重复标志
while(1){
bzero(&user.msg,sizeof(user.msg));
//接收时需要知道发送方
recvfrom(sfd,&user,sizeof(user),0,(struct sockaddr*)&cin,&addrlen);
//用户登录时把用户名,IP,PORT存入数据库中
//登录时用户名不可以和数据库中用户名重复
if(1==user.type){
do{
res=insert_sheet(db,user.name,cin);
sendto(sfd,&res,sizeof(res),0,(struct sockaddr *)&cin,addrlen);
if(res!=0){
recvfrom(sfd,&user,sizeof(user),0,(struct sockaddr*)&cin,&addrlen);
}
}while(res);
//转发给在线的其他用户该用户登录成功
temp.user=user;
search(db,user.name,&temp);
//在服务器打印该客户登录成功
printf("[%s %d] user:%s 登录成功\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),user.name);
}
//用户聊天时服务器转发客户端消息
else if(2==user.type){
//转发客户端消息
temp.user=user;
search(db,user.name,&temp);
//在服务器打印用户聊天成功
printf("[%s %d] user:%s chat成功\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),user.name);
}
else if(3==user.type){
//用户下线删除用户在数据库中信息
del_sheet(db,user.name);
//给其他用户发送该客户下线信息
temp.user=user;
search(db,user.name,&temp);
//服务器打印该用户下线
printf("[%s %d] user:%s offline\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),user.name);
}
}
//5.关闭套接字
close(sfd);
return 0;
}
func_ser.c
#include "ser.h"
//服务器发送系统信息线程
void *task(void *arg){
while(1){
th_t th=*((th_t*)arg);
user_t system1; //系统消息结构体变量
memset(system1.msg,0,sizeof(system1.msg));
system1.type=4; //发送系统信息状态码为4
fgets(system1.msg,sizeof(system1.msg),stdin); //从终端获取发送的消息
system1.msg[strlen(system1.msg)-1]='\0';
//调用search传参
temp_t temp;
temp.th=th;
temp.user=system1;
search(th.db,"",&temp); //调用search函数查找在线的全部用户
}
}
//创建表
int create_sheet(sqlite3 *db){
//sql语句,并将用户名设置为主键,重复插入则报错
char sql[128]="create table if not exists user (name char primary key,\
IP char,PORT short);";
//errmsg
char *errmsg=NULL;
//使用sqlite3_exec函数调用sqlite3语句创建表格
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg)!=SQLITE_OK){
fprintf(stderr,"__%d__%s\n",__LINE__,errmsg);
return -1;
}
return 0;
}
//在表中插入数据
int insert_sheet(sqlite3 *db,char *name,struct sockaddr_in cin){
//sql语句
char sql[128]="";
//把格式串转换成字符串,数据库插入语句
sprintf(sql,"insert into user values (\"%s\",\"%s\",%d)",name,inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
char *errmsg=NULL;
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg)!=SQLITE_OK){
fprintf(stderr,"__%d__%s\n",__LINE__,errmsg);
return -1;
}
return 0;
}
//删除表中的数据
int del_sheet(sqlite3 *db,char *name){
//sql语句
char sql[128]="";
//将格式串转化为字符串,数据库删除语句
sprintf(sql,"delete from user where name=\"%s\";",name);
char *errmsg;
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg)!=SQLITE_OK){
fprintf(stderr,"__%d__%s\n",__LINE__,errmsg);
return -1;
}
return 0;
}
//删除表
int del_sheet_a(sqlite3 *db){
//将格式串转化为字符串,数据库删除
char sql[128]="delete from user";
char *errmsg;
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg)!=SQLITE_OK){
fprintf(stderr,"__%d__%s",__LINE__,errmsg);
return -1;
}
}
//查找表中的名字不为name的成员
int search(sqlite3 *db,char *name,void *arg){
//给回调函数传参
temp_t temp=*(temp_t *)arg;
char sql[128]="";
//数据库查找语句
sprintf(sql,"select * from user where name != \"%s\";",name);
// 错误信息
char *errmsg;
if(sqlite3_exec(db,sql,select_callback,&temp,&errmsg)!=SQLITE_OK){
fprintf(stderr,"__%d__%s\n",__LINE__,errmsg);
return -1;
}
}
//search回调函数(sfd,user)
int select_callback(void *arg,int columns,char **column_text,char **colum_name){
// printf("columns=%d\n",columns);
//存放从数据库中查找出的ip和端口号的临时变量
temp_t temp=*((temp_t *)arg);
struct sockaddr_in cin;
socklen_t addrlen=sizeof(cin);
//将查找出的数据填充
cin.sin_family=AF_INET;
cin.sin_port=htons(atoi(*(column_text+2)));
cin.sin_addr.s_addr=inet_addr(*(column_text+1));
// printf("port=%#x\n",cin.sin_port);
// printf("port=%#x\n",cin.sin_addr.s_addr);
// printf("column_text=%s colum_text=%s\n",*(column_text+1),*(column_text+2));
if(sendto(temp.th.sfd,&(temp.user),sizeof(temp.user),0,(struct sockaddr*)&cin,addrlen)<0){
ERR_MSG("sendto");
return -1;
}
return 0;
}
makefile
EXE=char_ser
OBJECTS=$(patsubst %.c,%.o,$(wildcard *.c))
CC=gcc
CFLAGS+=-c
CFLAGS+=-o
all:$(EXE)
$(EXE):$(OBJECTS)
$(CC) $^ -pthread -lsqlite3 -o $@
%.o:%.c
$(CC) $(CFLAGS) $@ $^
clean:
rm $(OBJECTS) $(EXE)
客户端
cli.h
#ifndef __CLI_H__
#define __CLI_H__
#include <myhead.h>
//UDP实现网络聊天室
//UDP客户端
#define IP "192.168.125.13" //服务器IP地址
#define PORT 6666 //服务器端口号
#define ERR_MSG(msg) do{fprintf(stderr,"__%d__",__LINE__);\
perror(msg);\
}while(0)
typedef struct
{
int type; //用户状态:定义1为登录、2为聊天,3为客户端下线,4为系统消息
char name[128]; //用户名
char msg[128]; //消息
}user_t;
//线程传参
typedef struct
{
int cfd;
struct sockaddr_in serin;
socklen_t addrlen;
user_t user;
}temp_t;
void *task(void *arg); //读线程
#endif
main.c
#include "cli.h"
int main(int argc, const char *argv[])
{
int cfd; //客户端套接字文件描述符
//1.打开套接字
cfd=socket(AF_INET,SOCK_DGRAM,0);
if(cfd<0){
ERR_MSG("socket");
return -1;
}
//2.绑定非必须
//
struct sockaddr_in serin; //服务器地址信息结构体
//填充服务器结构体
serin.sin_family=AF_INET;
serin.sin_port=htons(PORT);
serin.sin_addr.s_addr=inet_addr(IP);
socklen_t addrlen=sizeof(serin);
//线程传参
temp_t temp;
temp.cfd=cfd;
temp.serin=serin;
temp.addrlen=addrlen;
//用户登录
//给服务器发送用户名,知道输入的用户名不与数据库中重名
int res;
user_t user; //消息结构体变量
do{
//从终端获取用户名
printf("\033[1;33;10m请输入用户名\033[0m>>");
memset(user.name,0,sizeof(user.name));
fgets(user.name,sizeof(user.name),stdin);
user.name[strlen(user.name)-1]='\0';
user.type=1; //设置为登录状态
//给服务器传用户名
if(sendto(cfd,&user,sizeof(user),0,(struct sockaddr*)&serin,sizeof(serin))<0){
ERR_MSG("sendto");
return -1;
}
//接收服务查询结果
recvfrom(cfd,&res,sizeof(res),0,(struct sockaddr*)&serin,&addrlen);
if(res!=0){
printf("\033[10;31;10m用户名重复\n\033[0m");
}
}while(res);
temp.user=user;
//创建分支线程运行接收数据
pthread_t tid;
if(pthread_create(&tid,NULL,task,&temp)!=0){
fprintf(stderr,"pthread_create error\n");
return -1;
}
//登录成功设置为聊天状态
user.type=2;
//3发送
while(1){
bzero(user.msg,sizeof(user.msg));
//从终端输入要发送的消息
printf("\033[1;32;10m%s:\033[0m",user.name);
fgets(user.msg,sizeof(user.msg),stdin);
user.msg[strlen(user.msg)-1]='\0';
//给服务器发送数据,如果为quit,设定为下线状态
if(0==strcmp(user.msg,"quit")){
user.type=3;
}
//给服务器发送信息
sendto(cfd,&user,sizeof(user),0,(struct sockaddr*)&serin,sizeof(serin));
//如果为下线状态则退出客户端
if(3==user.type){
close(cfd);
exit(EXIT_SUCCESS);
}
}
pthread_join(tid,NULL);
return 0;
}
func_cli.c
#include "cli.h"
//读取数据
void *task(void *arg){
//主线程传参
temp_t temp=*((temp_t*)arg);
//接收服务器信息临时变量
user_t user1;
while(1){
memset(&user1,0,sizeof(user1));
//接收服务器信息
recvfrom(temp.cfd,&user1,sizeof(user1),0,(struct sockaddr*)&(temp.serin),&(temp.addrlen));
if(1==user1.type){
//用户登录接收用户登录信息
printf("\033[128D \033[1;33;10muser:%s online\n\033[0m",user1.name);
// printf("请输入>>");
}
else if(2==user1.type){
//用户聊天接收聊天信息
printf("\033[128D \033[1;33;10m%s:%s\n\033[0m",user1.name,user1.msg);
}
else if(3==user1.type){
//用户下线信息
printf("\033[128D \033[1;33;10muesr:%s offline\n\033[0m",user1.name);
}
else if(4==user1.type){
//系统信息
printf("\033[128D \033[1;31;10mSYSTEM:%s\n\033[0m",user1.msg);
}
//读取完后刷新出最后一行用户名
printf("\033[1;32;10m%s:\033[0m",temp.user.name);
fflush(stdout);
}
}
makfile
EXE=char_cli
OBJECTS=$(patsubst %.c,%.o,$(wildcard *.c))
CC=gcc
CFLAGS+=-c
CFLAGS+=-o
all:$(EXE)
$(EXE):$(OBJECTS)
$(CC) $^ -pthread -o $@
%.o:%.c
$(CC) $(CFLAGS) $@ $^
clean:
rm $(OBJECTS) $(EXE)