Socket | C语言 | Linux | 网络编程 | 实战(一)_我是一块小石头-CSDN博客
Socket | C语言 | Linux | 网络编程 | 实战(二)_我是一块小石头-CSDN博客
Socket | C语言 | Linux | 网络编程 | 实战(三)_我是一块小石头-CSDN博客
实例
通过select实现单server对多client的交互
Client
/*client端*/
#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include <stdbool.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFFER_SIZE 1024
// int fd;
// bool open_dev()
// {
// fd = open("rec_msg", O_CREAT | O_RDWR | O_APPEND,0600);
// if(fd < 0){
// printf("rec_msg is open fail!\n");
// return false;
// }
// return true;
// }
int main(int argc, const char * argv[])
{
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(11332);
server_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
bzero(&(server_addr.sin_zero), 8);
//建立socket连接,AF_INET : TCP/IP-IPV4,SOCK_STREAM : TCP类型
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if(server_sock_fd == -1)
{
perror("socket error");
return 1;
}
char recv_msg[BUFFER_SIZE];
char input_msg[BUFFER_SIZE];
//TCP,客户端主动连接服务器
if(connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) == 0)
{
fd_set client_fd_set; //一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄
struct timeval tv; //它指明我们要等待的时间:
// open_dev();//打开驱动的设备节点.无则删除
while(1)
{
tv.tv_sec = 20;//秒
tv.tv_usec = 0;//毫秒
FD_ZERO(&client_fd_set); //将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化
FD_SET(STDIN_FILENO, &client_fd_set);//用于在文件描述符集合中增加一个新的文件描述符。
/*
STDIN_FILENO:接收键盘的输入
STDOUT_FILENO:向屏幕输出
*/
FD_SET(server_sock_fd, &client_fd_set);
//参数maxfd是需要监视的最大的文件描述符值+1;
//int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
/*
int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。
fd_set*readfds是指向fd_set结构的 指针,这个集合中应该包括 文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
fd_set*writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。
struct timeval *timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以 形参传入,即不传入时间结构,就是将select置于 阻塞状态,一定等到监视 文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非 阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
*/
select(server_sock_fd + 1, &client_fd_set, NULL, NULL, &tv);
if(FD_ISSET(STDIN_FILENO, &client_fd_set)) //用于测试指定的文件描述符是否在该集合中。
{
bzero(input_msg, BUFFER_SIZE); //置字节字符串s的前n个字节为零且包括‘\0’
fgets(input_msg, BUFFER_SIZE, stdin);
if(send(server_sock_fd, input_msg, BUFFER_SIZE, 0) == -1)
{
perror("发送消息出错!\n");
}
}
if(FD_ISSET(server_sock_fd, &client_fd_set))
{
bzero(recv_msg, BUFFER_SIZE);
long byte_num = recv(server_sock_fd, recv_msg, BUFFER_SIZE, 0);
if(byte_num > 0)
{
if(byte_num > BUFFER_SIZE)
{
byte_num = BUFFER_SIZE;
}
recv_msg[byte_num] = '\0';
/*此处可以添加控制信息*/
printf("服务器:%s\n", recv_msg);
}
else if(byte_num < 0)
{
printf("接受消息出错!\n");
}
else
{
printf("服务器端退出!\n");
exit(0);
}
if(strcmp(recv_msg,"quit") == 0 ||strcmp(recv_msg, "allquit") == 0){
printf("End conversation.\n");
break;
}
}
}
}
return 0;
}
Server
/*server端*/
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#define BACKLOG 5 //完成三次握手但没有accept的队列的长度
#define CONCURRENT_MAX 8 //应用层同时可以处理的连接
#define SERVER_PORT 11332
#define BUFFER_SIZE 1024
#define QUIT_CMD ".quit"
int client_fds[CONCURRENT_MAX];
int main(int argc, const char *argv[])
{
char input_msg[BUFFER_SIZE];
char recv_msg[BUFFER_SIZE];
//本地地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
bzero(&(server_addr.sin_zero), 8);
//创建socket
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock_fd == -1)
{
perror("socket error");
return 1;
}
//绑定socket
int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (bind_result == -1)
{
perror("bind error");
return 1;
}
//listen
if (listen(server_sock_fd, BACKLOG) == -1)
{
perror("listen error");
return 1;
}
//fd_set
fd_set server_fd_set;
int max_fd = -1;
struct timeval tv; //超时时间设置
while (1)
{
tv.tv_sec = 20;
tv.tv_usec = 0;
FD_ZERO(&server_fd_set);
//将STDIN_FILENO添加入set中
FD_SET(STDIN_FILENO, &server_fd_set);
if (max_fd < STDIN_FILENO)
{
max_fd = STDIN_FILENO;
}
//printf("STDIN_FILENO=%d\n", STDIN_FILENO);
//服务器端socket,添加到set中
FD_SET(server_sock_fd, &server_fd_set);
// printf("server_sock_fd=%d\n", server_sock_fd);
if (max_fd < server_sock_fd)
{
max_fd = server_sock_fd;//select需要max_fd+1
}
//客户端连接
for (int i = 0; i < CONCURRENT_MAX; i++)
{
//printf("client_fds[%d]=%d\n", i, client_fds[i]);
if (client_fds[i] != 0)
{
FD_SET(client_fds[i], &server_fd_set);
if (max_fd < client_fds[i])
{
max_fd = client_fds[i];//select需要max_fd+1
}
}
}
int ret = select(max_fd + 1, &server_fd_set, NULL, NULL, &tv);
if (ret < 0)
{
perror("select 出错\n");
continue;
}
else if (ret == 0)
{
printf("select 超时\n");
continue;
}
else
{
//ret 为未状态发生变化的文件描述符的个数
if (FD_ISSET(STDIN_FILENO, &server_fd_set))
{
printf("发送消息:\n");
bzero(input_msg, BUFFER_SIZE);
fgets(input_msg, BUFFER_SIZE, stdin);
//输入“.quit"则退出服务器
if (strcmp(input_msg, QUIT_CMD) == 0)
{
exit(0);
}
for (int i = 0; i < CONCURRENT_MAX; i++)
{
if (client_fds[i] != 0)
{
printf("client_fds[%d]=%d\n", i, client_fds[i]);
send(client_fds[i], input_msg, BUFFER_SIZE, 0);
}
}
}
if (FD_ISSET(server_sock_fd, &server_fd_set))
{
//有新的连接请求
struct sockaddr_in client_address;
socklen_t address_len;
int client_sock_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);
printf("new connection client_sock_fd = %d\n", client_sock_fd);
if (client_sock_fd > 0)
{
int index = -1;
for (int i = 0; i < CONCURRENT_MAX; i++)
{
if (client_fds[i] == 0)
{
index = i;
client_fds[i] = client_sock_fd;
break;
}
}
if (index >= 0)
{
printf("新客户端(%d)加入成功 %s:%d\n", index, inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
}
else
{
bzero(input_msg, BUFFER_SIZE);
strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");
send(client_sock_fd, input_msg, BUFFER_SIZE, 0);
printf("客户端连接数达到最大值,新客户端加入失败 %s:%d\n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
}
}
}
for (int i = 0; i < CONCURRENT_MAX; i++)
{
if (client_fds[i] != 0)
{
if (FD_ISSET(client_fds[i], &server_fd_set))
{
//处理某个客户端过来的消息
bzero(recv_msg, BUFFER_SIZE);
long byte_num = recv(client_fds[i], recv_msg, BUFFER_SIZE, 0);
if (byte_num > 0)
{
if (byte_num > BUFFER_SIZE)
{
byte_num = BUFFER_SIZE;
}
//recv_msg[byte_num] = '\0';
printf("客户端(%d):%s", i, recv_msg);
/*转发数据给其他的客户端*/
for (int i = 0; i < CONCURRENT_MAX; i++)
{
if (client_fds[i] != 0)
{
send(client_fds[i], recv_msg, sizeof(recv_msg), 0);
}
}
/*结束转发内容*/
}
else if (byte_num < 0)
{
printf("从客户端(%d)接受消息出错.\n", i);
}
else
{
FD_CLR(client_fds[i], &server_fd_set);
client_fds[i] = 0;
printf("客户端(%d)退出了\n", i);
}
}
}
}
}
}
return 0;
}