#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <time.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
#include "vector_fd.h"
#include <fcntl.h>
/*基于I/O多路复用的高并发服务器编程
测试:telnet 127.0.0.1 xxxx
http://xxx.xxx.xxx.xxx:端口号
注意:演示时可关闭服务器的防火墙,防火墙口被过滤
#service iptables status 查看防火墙
#service iptables stop 关闭防火墙
*/
VectorFD* vfd;
int sockfd;
int bStop = 0;
void sig_handler(int signo)
{
if(signo == SIGINT){
bStop = 1;
printf("server close\n");
close(sockfd);
destroy_vector_fd(vfd);
exit(1);
}
}
void out_addr(struct sockaddr_in* clientAddr)
{
char ip[16];
memset(ip, 0, sizeof(ip));
int port = ntohs(clientAddr->sin_port);
inet_ntop(AF_INET, &clientAddr->sin_addr.s_addr, ip, sizeof(ip));
printf("%s(%d) connnected!\n", ip, port);
}
/*服务程序
* fd对应于某个连接的客户端,和某一个连接的客户端进行双向通信
*/
void do_service(int fd)
{
/*服务端和客户端进行读写操作(双向通信)*/
char buff[512];
memset(buff, 0, sizeof(buff));
size_t size = read(fd, buff, sizeof(buff));
//读取客户端发送过来的消息
//若读不到数据直接返回了,直接服务于下一个客户端
//因此不需要判断size小于0的情况。
if(size == 0){ //客户端己关闭连接
char info[] = "client close\n";
write(STDOUT_FILENO, info, sizeof(info));
//将fd从动态数组中删除
remove_fd(vfd, fd);
close(fd);
}else if(size > 0){
write(STDOUT_FILENO, buff, sizeof(buff));//显示客户端发送的消息
//写回客户端(回显功能)
if(write(fd, buff, sizeof(buff)) != size){
if(errno == EPIPE){
//如果客户端己被关闭(相当于管道的读端关闭),会产生SIGPIPE信号
//并将errno设置为EPIPE
perror("write error");
remove_fd(vfd, fd);
close(fd);
}
}
}
}
//遍历动态数组中所有的socket描述符,并将之加入到fd_set中。
//同时此函数返回动态数组中最大的那个描述符
static int add_set(fd_set* set)
{
FD_ZERO(set); //清空描述符集
int max_fd = vfd->fd[0];
int i=0;
for(; i<vfd->counter; i++){
int fd = get_fd(vfd, i);
if(fd > max_fd)
max_fd = fd;
FD_SET(fd, set); //将fd加入到fd_set中
}
return max_fd;
}
//线程函数
void* th_fn(void* arg)
{
struct timeval t;
t.tv_sec = 2;
t.tv_usec = 0;
int n = 0; //返回select返回的准备好的socket数量
int maxfd; //所有socket描述符的最大值
fd_set set;
maxfd = add_set(&set);
/*
* 调用select函数会阻塞,委托内核去检查传入的描述符集是否有socket己准备好,
* 若有,则返回准备好的socket数量,超时则返回0
* 第1个参数为fd_set中socket的范围(最大描述符+1)
*/
while(((n = select(maxfd + 1, &set, NULL, NULL, &t)) >=0) && (!bStop)){
if(n > 0){
int i = 0;
//检测哪些socket准备好,并和这些准备好的socket对应的客户端进行双向通信
for(; i<vfd->counter; i++){
int fd = get_fd(vfd, i);
if(FD_ISSET(fd, &set)){
do_service(fd);
}
}
}
//重新设置时间
t.tv_sec = 2;
t.tv_usec = 0;
//清空描述符集
//重新遍历动态数组中最新的描述符,并放置到fd_set
maxfd = add_set(&set);
}
return (void*)0;
}
int main(int argc, char* argv[])
{
if(argc < 2){
printf("usage: %s port\n", argv[0]);
exit(1);
}
//按ctrl-c时中止服务端程序
if(signal(SIGINT, sig_handler) == SIG_ERR){
perror("signal sigint error");
exit(1);
}
/*步骤1:创建socket(套接字)
*注:socket创建在内核中,是一个结构体
*AF_INET:IPv4
*SOCK_STREAM:tcp协议
*/
sockfd = socket(AF_INET, SOCK_STREAM, 0);
/*步骤2:将sock和地址(包括ip、port)进行绑定*/
struct sockaddr_in servAddr; //使用专用地址结构体
memset(&servAddr, 0, sizeof(servAddr));
//往地址中填入ip、port和Internet地址族类型
servAddr.sin_family = AF_INET;//IPv4
servAddr.sin_port = htons(atoi(argv[1])); //port
servAddr.sin_addr.s_addr = INADDR_ANY; //任一可用的IP
if(bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) <0 ){
perror("bind error");
exit(1);
}
/*步骤3:调用listen函数启动监听
* 通知系统去接受来自客户端的连接请求
*/
if(listen(sockfd, 10) < 0){ //队列中最多允许10个连接请求
perror("listen error");
exit(1);
}
//创建放置套接字描述符的动态数组
vfd = create_vector_fd();
//设置线程的分离属性
pthread_t th;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
//启动子线程
int err;
if((err = pthread_create(&th, &attr, th_fn, (void*)0)) != 0){
perror("pthread create error");
exit(1);
}
pthread_attr_destroy(&attr);
/*(1)主线程获得客户端连接,将新的socket描述符放置到动态数组中
*(2)子线程的任务
A.调用select委托内核去检查传入到select中的描述符是否准备好
* B.利用FD_ISSET来找出准备好的那些描述符并和对应的客户端进行双向通信
*/
struct sockaddr_in clientAddr;
socklen_t len = sizeof(clientAddr);
while(!bStop){
/*步骤4:调用accept函数,从请求队列中获取一个连接
* 并返回新的socket描述符
* */
int fd = accept(sockfd, (struct sockaddr*)&clientAddr, &len);
if(fd < 0){
perror("accept error");
continue;
}
//输出客户端信息
out_addr(&clientAddr);
//将返回的新socket描述符加入到动态数组中
add_fd(vfd, fd);
}
close(sockfd);
destroy_vector_fd(vfd);
return 0;
}
/*输出结果
* [root@localhost 15.AdvNet]# gcc -o bin/echo_tcp_server_select -Iinclude bin/vector_fd.o src/echo_tcp_server_select.c -lpthread
* [root@localhost 15.AdvNet]# bin/echo_tcp_server_select 8888
* 127.0.0.1(40695) connnected!
* abcdefaaabbbcdef^Cserver close
*/
//echo_tcp_client.c(与上一例相同)
#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
int main(int argc, char* argv[])
{
if(argc < 3){
printf("usage: %s ip port\n", argv[0]);
exit(1);
}
/*步骤1: 创建socket(套接字)*/
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0){
perror("socket error");
}
//往servAddr中填入ip、port和地址族类型
struct sockaddr_in servAddr;
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(atoi(argv[2]));
//将ip地址转换成网络字节序后填入servAdd中
inet_pton(AF_INET, argv[1], &servAddr.sin_addr.s_addr);
/*步骤2: 客户端调用connect函数连接到服务器端*/
if(connect(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < 0){
perror("connect error");
exit(1);
}
/*步骤3: 调用自定义的协议处理函数和服务端进行双向通信*/
char buff[512];
size_t size;
char* prompt = ">";
while(1){
memset(buff, 0, sizeof(buff));
write(STDOUT_FILENO, prompt, 1);
size = read(STDIN_FILENO, buff, sizeof(buff));
if(size < 0) continue;
buff[size-1] = '\0';
//将键盘输入的内容发送到服务端
if(write(sockfd, buff, sizeof(buff)) < 0){
perror("write error");
continue;
}else{
memset(buff, 0, sizeof(buff));
//读取来自服务端的消息
if(read(sockfd, buff, sizeof(buff)) < 0){
perror("read error");
continue;
}else{
printf("%s\n", buff);
}
}
}
/*关闭套接字*/
close(sockfd);
}
优质内容筛选与推荐>>
1、【iOS开发】如何用 Swift 语言进行LBS应用的开发?2、MySQL的架构3、安装sysstat出现软件包依赖问题4、几句话总结一个算法之RNN、LSTM和GRU5、瑞鹏的第一次敲代码作业
长按二维码向我转账
受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。
阅读
好看
已推荐到看一看
你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
取消
分享想法到看一看
确定
最多200字,当前共字