首先声明一下代码中的,具体细节批注在代码当中!!!:
1. tcp4bind(socket + bind)、Accept(accept)、Listen(listen) 均是通过网络查询资料重写的函数,和原函数相比向里面添加了perror错误检查和其他的一些信号检查。读者可以直接改成原来linux自带的 socket、bind、accept、listen函数。
2.本代码实现了多个客户端向服务器发送消息,服务器收到后通过socket向客户端发送同样的消息并在客户端关闭后通过自定义信号回收子进程这一功能,置于读者想添加哪些别的功能,可以另行考虑
3.基本框架:
原进程自带的线程(服务器中负责监听客户端):建立socket,绑定bind,监听listen,最后只保留lfd(监听套接字)。
新创建的线程(服务器中负责和客户端收发数据):cfd(从完成队列提取连接调用accept函数新建的收发套接字)
#include <stdio.h>
#include <pthread.h>
#include "wrap.h"
typedef struct c_info
{
int cfd;
struct sockaddr_in cliaddr;
}CINFO;
void* client_fun(void *arg);
int main(int argc, char *argv[])
{
if(argc < 2)
{
printf("argc < 2???\n");//注意argc第一个是a.out 第二个才是参数
return 0;
}
short port = atoi(argv[1]);//atoi:把字符串转换成整型数的一个函数
int lfd = tcp4bind(port, NULL);//创建绑定,NULL代表通配地址
Listen(lfd, 128);
struct sockaddr_in cliaddr;//这里我们只需要输出一次客户端的信息即可,其他的线程用while循环替代上一个即可
socklen_t len = sizeof(cliaddr);
//CINFO info;可能主线程比其他创建出来的线程更快,快到调用完Accept函数后就把原先线程的结构体覆盖了
CINFO *info;//指针指向堆区,并用malloc在堆区开辟空间,malloc如果分配成功则返回指向被分配内存的指针
//而我们这里相当于定义了一个假的”全局变量“,在循环外面声明了指针类型和指针名称
while(1)
{
int cfd = Accept(lfd, (struct sockaddr *)&cliaddr, &len);
char ip[16]="";//注意这里不可以用*,必须要用已经开辟好空间的ip(长度为16)
pthread_t pthid;
info = malloc(sizeof(CINFO));//指针名称一样,地址不一样
info->cfd = cfd;
info->cliaddr = cliaddr;
pthread_create(&pthid, NULL, client_fun, info);//传两个以上的参数需要结构体
pthread_detach(pthid);
}
return 0;
}
void* client_fun(void *arg)
{
CINFO *info = (CINFO*)arg;//arg本身就是地址,直接传给都是指针类的info
char ip[16]="";
printf("new client ip = %s port = %d\n", inet_ntop(AF_INET, &(info->cliaddr.sin_addr.s_addr), ip, 16),ntohs(info->cliaddr.sin_port));
while(1)
{
char buf[1024]="";
int count = 0;
count = read(info->cfd, buf, sizeof(buf));
if(count < 0)
{
perror("read:");
break;
}
else if(count == 0)
{
printf("client close\n");
break;
}
else
{
printf("%s\n", buf);
write(info->cfd, buf, count);
}
}
close(info->cfd);
free(info);//堆区要手动释放
}
成果图: