1. 完善服务器代码, 做到给所有客户端发送数据;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;
int main(int argc, const char* argv[])
{
//1.创建套接字
//AF_INET为协议族 - ipv4协议族
//STREAM 流式套接字 - TCP
//0 -自动匹配协议
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("socket is err:");
return -1;
}
printf("sockfd = %d\n",sockfd); //3
int opt = 1;
//设置套接字的属性: 将套接字属性设置为 端口可重复使用的功能
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
//2.绑定套接字 bind
struct sockaddr_in saddr,caddr;
saddr.sin_family = AF_INET;
//主机字节序的端口号 转换为 网络字节序
saddr.sin_port = htons(atoi(argv[1])); //atoi 将字符串 转换为 整形数
//0.0.0.0 监听本机所有IPV4的地址
saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
int len = sizeof(caddr);
//绑定 填充的结构体属性 到 sockfd 套接字上
if(bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr)) < 0)
{
perror(" bind is err:");
return -1;
}
printf("sockaddr = %d sockaddr_in = %d\n",sizeof(struct sockaddr),sizeof(struct sockaddr_in));
//3. 监听 - 创建监听队列
if(listen(sockfd,5) < 0)
{
perror("listen is err:");
return -1;
}
//1.创建表
fd_set readfds,tempfds;
//2.清空表
FD_ZERO(&readfds);
//3.将关心文件描述符添加表
FD_SET(sockfd,&readfds);
FD_SET(0,&readfds);
int maxfd = sockfd;
char buf[128];
while(1)
{
//将原表的内容 拷贝到 备份表
tempfds = readfds;
//4.select轮训检测
select(maxfd+1,&tempfds,NULL,NULL,NULL);
//5.判断文件描述 在表内是否有事件
if(FD_ISSET(0,&tempfds))
{
//键盘输入
fgets(buf,sizeof(buf),stdin);
for(int i=4; i<=maxfd; i++)
{
if(FD_ISSET(i,&tempfds))
{
if(buf[strlen(buf)-1] == '\n')
buf[strlrn(buf)-1] = '\0';
//发送给所有客户端
send(i,buf,sizeof(buf),0);
}
}
}
if(FD_ISSET(sockfd,&tempfds)) //链接的文件描述符
{
//要建立通信了
int acceptfd = accept(sockfd,(struct sockaddr *)&caddr,&len);
if(acceptfd < 0)
{
perror("accept is err:");
return -1;
}
//将通信的文件描述符 添加到原表内
FD_SET(acceptfd,&readfds);
// 只有新的通信的文件描述符 > 最大文件描述符下值, 才会让maxfd的值改变
if(acceptfd > maxfd)
{
maxfd = acceptfd;
}
}
for(int i = 4; i <= maxfd; i++)
{
if(FD_ISSET(i,&tempfds))
{
int ret = recv(i,buf,sizeof(buf),0);
if(ret < 0)
{
perror("recv is err:");
return -1;
}
else if(ret == 0)
{
printf("client %d is exit\n",i);
close(i);
//删除表内的已退出的文件描述符
FD_CLR(i,&readfds);
//maxfd
if(i == maxfd)
{
maxfd--;
}
}
else
{
printf("client:%d : %s\n",i,buf);
}
}
}
}
close(sockfd);
return 0;
}
补充部分截图:
![](https://i-blog.csdnimg.cn/direct/5668a5daa3c041589ff927168fa9bfe9.png)
2. 利用select 实现 TCP客户端的全双工;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;
int main(int argc, const char* argv[])
{
//1.创建套接字
//AF_INET为协议族 - ipv4协议族
//STREAM 流式套接字 - TCP
//0 -自动匹配协议
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("sockfd is error:");
return -1;
}
//设置套接字的属性: 将套接字属性设置为 端口可重复使用的功能
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
//2.套接字
struct sockaddr_in saddr,caddr;
saddr.sin_family = AF_INET;
//主机字节序的端口号 转换为 网络字节序
saddr.sin_port = htons(atoi(argv[1])); //atoi 将字符串 转换为 整形数
//0.0.0.0 监听本机所有IPV4的地址
saddr.sin_addr.s_addr = inet_addr(argv[2]);
if(connect(sockfd,(struct sockaddr *)&saddr,sizeof(saddr)) < 0)
{
perror("connect is error:");
return -1;
}
//1.创建表
fd_set readfds,tempfds;
//2.清空表
FD_ZERO(&readfds);
//3.将关心文件描述符添加表
FD_SET(sockfd,&readfds);
FD_SET(0,&readfds);
int maxfd = sockfd;
char buf[128];
while(1)
{
//将原表的内容 拷贝到 备份表
tempfds = readfds;
//4.select轮训检测
if(select(maxfd+1,&tempfds,NULL,NULL,NULL) < 0)
{
perror("select is error:");
return -1;
}
//5.判断文件描述 在表内是否有事件
if(FD_ISSET(sockfd,&tempfds))
{
int res = recv(sockfd,buf,sizeod(buf),0);
if (res < 0)
{
perror("res is error:");
break;
}
else if(res == 0)
{
printf("res is exit:\n");
}
else
{
printf("client: %s\n",buf);
}
}
if(FD_ISSET(0,&tempfds))
{
fgets(buf,sizeof(buf),stdin);
if(buf[strlen(buf)-1] == '\n')
buf[strlen(buf)-1] = '\0';
send(sockfd,buf,sizeof(buf),0);
}
}
return 0;
}