sfd = socket();
bind();
listen();
while(1){
newfd = accept();
while(1){
recv();
send();
}
close(newfd);
}
close(sfd);
void handler(int sig){
while(waitpid(-1, NULL, WNOHANG) > 0);
}
signal(17, handler);
sfd = socket();
bind();
listen();
while(1){
newfd = accept();
cpid = fork();
if(0 == cpid){
close(sfd);
while(1){
recv();
send();
}
close(newfd);
exit(0); //退出子进程
}
close(newfd);
}
close(sfd);
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_MSG(msg) do{\
fprintf(stderr, "__%d__ ", __LINE__);\
perror(msg);\
}while(0)
#define PORT 8888 //端口号的网络字节序,1024~49151
#define IP "192.168.125.55" //本机IP,ifconfig
int deal_cli_msg(int newfd, struct sockaddr_in cin);
void handler(int sig)
{
//循环回收僵尸进程
//有子进程,没有僵尸进程 == 0
//没有子进程,也没有僵尸进程 ==-1、
while(waitpid(-1, NULL, WNOHANG) > 0);
return ;
}
int main(int argc, const char *argv[])
{
//捕获17号 SIGCHLD信号
if(signal(SIGCHLD, handler) == SIG_ERR)
{
ERR_MSG("signal");
return -1;
}
//创建流式套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success sfd=%d\n", sfd);
//允许端口快速复用
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
{
ERR_MSG("setsockopt");
return -1;
}
printf("允许端口快速复用成功\n");
//填充地址信息结构体给bind函数绑定使用;
//真实的地址信息结构体根据地址族指定,AF_INET:man 7 ip
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET;
sin.sin_port = htons(PORT); //端口号的网络字节序,1024~49151
sin.sin_addr.s_addr = inet_addr(IP); //本机IP的网络字节序,ifconfig
//绑定服务器的地址信息---》必须绑定
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success\n");
//将套接字转换成被动监听状态
if(listen(sfd, 128) < 0)
{
ERR_MSG("listen");
return -1;
}
printf("listen success\n");
struct sockaddr_in cin; //存储客户端的地址信息
socklen_t addrlen = sizeof(cin);
int newfd = -1;
pid_t cpid = -1;
while(1)
{
//父进程只负责连接
newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
if(newfd < 0)
{
ERR_MSG("accept");
return -1;
}
printf("[%s:%d] newfd=%d 客户端连接成功__%d__\n", \
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);
//能运行到当前位置,则代表有客户端连接成功,
//此时需要创建一个子进程,专门用于与客户端交互
cpid = fork();
if(0 == cpid)
{
close(sfd); //子进程只负责交互,sfd没有用
deal_cli_msg(newfd, cin);
close(newfd);
exit(0); //退出子进程,子进程只负责交互,不允许回到accept函数。
}
else if(cpid < 0)
{
ERR_MSG("fork");;
return -1;
}
close(newfd); //在父进程中newfd没有用
}
//关闭套接字
close(sfd);
return 0;
}
//子进程负责与客户端交互的函数
int deal_cli_msg(int newfd, struct sockaddr_in cin)
{
char buf[128] = "";
ssize_t res = 0;
while(1)
{
bzero(buf, sizeof(buf));
//接收数据
res = recv(newfd, buf, sizeof(buf), 0);
if(res < 0)
{
ERR_MSG("recv");
return -1;
}
else if(0 == res)
{
printf("[%s:%d] newfd=%d 客户端下线__%d__\n", \
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);
break;
}
printf("[%s:%d] newfd=%d : %s __%d__\n", \
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, buf, __LINE__);
if(strcmp(buf, "quit") == 0)
break;
//发送数据
strcat(buf, "*_*"); //数据可以选择冲终端获取
if(send(newfd, buf, sizeof(buf), 0) < 0)
{
ERR_MSG("send");
return -1;
}
printf("send success\n");
}
return 0;
}
sfd = socket();
bind();
listen();
while(1){
newfd = accept();
pthread_create( , , deal_cli_msg, );
pthread_detach(tid);
}
close(sfd);
void* deal_cli_msg(void* arg){
while(1){
recv();
send();
}
close(newfd);
pthread_exit();
}
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_MSG(msg) do{\
fprintf(stderr, "__%d__ ", __LINE__);\
perror(msg);\
}while(0)
#define PORT 8888 //端口号的网络字节序,1024~49151
#define IP "192.168.125.55" //本机IP,ifconfig
//传递给线程执行体的数据封装成结构体
struct Climsg
{
int newfd;
struct sockaddr_in cin;
};
void* deal_cli_msg(void* arg) ; //void* arg = &info
int main(int argc, const char *argv[])
{
//创建流式套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success sfd=%d\n", sfd);
//允许端口快速复用
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
{
ERR_MSG("setsockopt");
return -1;
}
printf("允许端口快速复用成功\n");
//填充地址信息结构体给bind函数绑定使用;
//真实的地址信息结构体根据地址族指定,AF_INET:man 7 ip
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET;
sin.sin_port = htons(PORT); //端口号的网络字节序,1024~49151
sin.sin_addr.s_addr = inet_addr(IP); //本机IP的网络字节序,ifconfig
//绑定服务器的地址信息---》必须绑定
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success\n");
//将套接字转换成被动监听状态
if(listen(sfd, 128) < 0)
{
ERR_MSG("listen");
return -1;
}
printf("listen success\n");
struct sockaddr_in cin; //存储客户端的地址信息
socklen_t addrlen = sizeof(cin);
int newfd = -1;
pthread_t tid; //存储线程tid号
struct Climsg info;
while(1)
{
//代码先阻塞在accept函数,再断开连接关闭的文件描述符时
//accept函数每次在阻塞的时候,会先预选一个没有被占用的文件描述符
//当解除阻塞的时候,若预选的文件描述符没有被占用,则直接返回预选的文件描述符
//若预选的文件描述符被占用,则会重新遍历一个没有被使用的文件名描述符返回
//主线程只负责连接(accept)
newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
if(newfd < 0)
{
ERR_MSG("accept");
return -1;
}
printf("[%s:%d] newfd=%d 客户端连接成功__%d__\n", \
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);
info.newfd = newfd;
info.cin = cin;
//能运行到当前位置,则代表有客户端连接成功,
//此时需要创建一个分支线程,专门用于处理客户端的交互
if(pthread_create(&tid, NULL, deal_cli_msg, (void*)&info) != 0)
{
fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
return -1;
}
pthread_detach(tid); //分离线程
}
//关闭套接字
close(sfd);
return 0;
}
//线程执行体 ---> 分支线程只负责交互
void* deal_cli_msg(void* arg) //void* arg = &info
{
//newfd和cin必须另存,每个客户端都有自己独立的通信文件描述符和地址信息。
//如果使用全局变量,或者指针方式间接访问,会导致所有线程共用一份newfd和cin,
//那么newfd和cin会被覆盖
int newfd = ((struct Climsg*)arg)->newfd;
struct sockaddr_in cin = ((struct Climsg*)arg)->cin;
char buf[128] = "";
ssize_t res = 0;
while(1)
{
bzero(buf, sizeof(buf));
//接收数据
res = recv(newfd, buf, sizeof(buf), 0);
if(res < 0)
{
ERR_MSG("recv");
break;
}
else if(0 == res)
{
printf("[%s:%d] newfd=%d 客户端下线__%d__\n", \
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);
break;
}
printf("[%s:%d] newfd=%d : %s __%d__\n", \
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, buf, __LINE__);
if(strcmp(buf, "quit") == 0)
break;
//发送数据
strcat(buf, "*_*"); //数据可以选择冲终端获取
if(send(newfd, buf, sizeof(buf), 0) < 0)
{
ERR_MSG("send");
break;
}
printf("send success\n");
}
close(newfd);
pthread_exit(NULL);
}
read阻塞:
文件描述符有阻塞属性:0号文件描述符有阻塞属性 + 读属性---》0号文件描述符加上非阻塞属性
1. 先获取0号文件描述符原有属性
2. 在原有属性的基础上将阻塞 设置为 非阻塞
3. 将修改后的属性重新设置回0号文件描述符中
功能:
获取/设置文件描述属性;
原型:
#include
#include
int fcntl(int fd, int cmd, ... /* arg */ );
参数:
int fd:指定要设置或者获取属性的文件描述符
int cmd:
F_GETFL (void):获取属性,第三个参数不用填,获取到的属性在返回值返回;
F_SETFL (int):设置属性,第三个参数是int类型;

功能:
阻塞函数,让内核监测集合中是否有文件描述符准备就绪,若准备就绪则解除阻塞;
当函数解除阻塞后,集合中会只剩下产生事件的文件描述符;例如:
0号准备就绪,则集合中只剩下0号
sfd准备就绪,则集合中只能下sfd;
0和sfd均准备就绪,则0和sfd均存在
若不将数据从触发事件的文件描述符对应的空间中取出,此时该文件描述符一直处于就绪状态。
原型:
#include
#include
#include
#include
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
参数:
int nfds:需要填充三个集合中最大的文件描述符编号+1;
fd_set *readfds, fd_set *writefds,
fd_set *exceptfds:读集合,写,其他集合,
若集合不使用,填NULL; 一般只用读集合;
struct timeval *timeout:设置超时时间;
1. 若不想设置超时时间,填NULL,则当前函数会一直阻塞,直到集合中有文件描述符准备就绪。
2. 设置超时时间; 若时间到后依然没有事件产生,则该函数解除阻塞,且返回失败情况。
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
返回值:
>0, 成功返回成功触发事件的文件描述符个数;
=0, 超时了;
失败,返回-1,更新errno;
操作集合的函数
void FD_CLR(int fd, fd_set *set); //将fd从集合中删除
int FD_ISSET(int fd, fd_set *set); //判断fd是否在集合中
void FD_SET(int fd, fd_set *set); //将fd添加到结合中
void FD_ZERO(fd_set *set); //清空集合
sfd = socket();
bind();
listen();
while(1){
tempfds = readfds;
select(maxfd+1, &tempfds, NULL, NULL, NULL);
for(int i=0; i<=maxfd; i++){
if(FD_ISSET(i, &tempfds) == 0) continue;
if(0 == i){
fgets();
}
else if(sfd == i){
newfd = accept()
FD_SET(newfd, &readfds);
maxfd = maxfd>newfd?maxfd:newfd;
}
else{
res = recv(i, );
if(0 == res){
close(i);
FD_CLR(i, &readfds);
while(!FD_ISSET(maxfd, &readfds) && maxfd-->=0);
}
send();
}
}
}
close(sfd);
这俩的代码都在这个链接里面,再放进来就太长了,写文档都在卡
功能:
阻塞函数,阻塞等待集合中有文件描述符准备就绪,若准备就绪,则立即解除阻塞。
原型:
#include
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数:
struct pollfd *fds:指定要监测的集合
struct pollfd {
int fd; /* file descriptor */ 指定要监测的文件描述符
short events; /* requested events */ 指定要监测的事件
short revents; /* returned events */ 实际产生的事件
};
事件:
POLLIN 这里有数据可读
POLLOUT 可写
POLLERR 错误事件,只有在revents中有效
nfds_t nfds:指定要监测的文件描述符的个数;
int timeout:超时时间,以ms为单位
>0, 设置超时时间,以ms为单位;
=0, 不阻塞,即使没有文件描述符准备就绪,该函数不阻塞;
<0, 不设置超时时间,一直阻塞,直到当集合中有文件描述符准备就绪,该函数解除阻塞;
返回值:
>0, 实际产生事件的文件描述符个数;
=0, 超时了
=-1,更新errno;
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_MSG(msg) do{\
fprintf(stderr, "__%d__ ", __LINE__);\
perror(msg);\
}while(0)
#define SER_PORT 8888 //服务器绑定的端口号
#define SER_IP "192.168.125.55" //服务器绑定的IP
int main(int argc, const char *argv[])
{
//创建流式套接字
int cfd = socket(AF_INET, SOCK_STREAM, 0);
if(cfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success cfd=%d\n", cfd);
//允许端口快速复用
int reuse = 1;
if(setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
{
ERR_MSG("setsockopt");
return -1;
}
printf("允许端口快速复用成功\n");
//绑定客户端的地址信息---》非必须绑定
//客户端若不绑定,则操作系统会自动给客户端绑定本机IP及随机端口
//填充服务器的地址信息结构体给connect函数使用;
//真实的地址信息结构体根据地址族指定,AF_INET:man 7 ip
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET;
sin.sin_port = htons(SER_PORT); //服务器绑定的端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); //服务器绑定的IP
//连接服务器,想要连接哪个服务器就需要填充哪个服务器绑定的地址信息
if(connect(cfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("connect");
return -1;
}
printf("connect server success\n");
//创建集合
struct pollfd fds[2] = {0};
//将需要的文件描述符添加到集合中
fds[0].fd = 0; //指定要监测0号文件描述符
fds[0].events = POLLIN; //指定要监测读事件
fds[1].fd = cfd; //指定要监测cfd
fds[1].events = POLLIN;
int p_res = 0;
char buf[128] = "";
ssize_t res = 0;
while(1)
{
p_res = poll(fds, sizeof(fds)/sizeof(struct pollfd), -1);
if(p_res < 0)
{
ERR_MSG("poll");
return -1;
}
else if(0 == p_res)
{
printf("time out");
break;
}
//能运行到当前位置,则代表集合中有文件描述符准备就绪
//即revents成员中有数据了,
//判断revents中是否有POLLIN事件
if(fds[0].revents & POLLIN)
{
printf("触发键盘输入事件\n");
bzero(buf, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf)-1] = '\0';
//发送数据
if(send(cfd, buf, sizeof(buf), 0) < 0)
//if(write(cfd, buf, sizeof(buf)) < 0)
{
ERR_MSG("send");
return -1;
}
printf("send success\n");
}
if(fds[1].revents & POLLIN)
{
bzero(buf, sizeof(buf));
//接收数据
res = recv(cfd, buf, sizeof(buf), 0);
//res = read(cfd, buf, sizeof(buf));
if(res < 0)
{
ERR_MSG("recv");
return -1;
}
else if(0 == res)
{
printf("[%s:%d] cfd=%d 服务器下线__%d__\n", \
SER_IP, SER_PORT, cfd, __LINE__);
break;
}
printf("[%s:%d] cfd=%d :%s __%d__\n", \
SER_IP, SER_PORT, cfd, buf, __LINE__);
}
}
//关闭套接字
close(cfd);
return 0;
}