Linux IO模型(6.11)

[1] I/O模型
    进程操作文件时, 文件没有就绪(ready)
    (
    read--文件(设备或通讯)没有数据,
    write--文件(设备或通讯)已满,
    recv/recvfrom--套接字缓冲区空时(流式套接字),没有接收到数据包(报文套接字)
    send/sendto--套接字缓冲区已满(流式套接字)
    )时,操作系统会,采取如下策略实现系统调用:
    1. block(阻塞)
       让进程在上面的系统调用中,休眠等待。
       
    2. noblock(非阻塞)
       让进程立即返回错误,错误码如下:
       EAGAIN          对于非socket文件
       EWOULDBLOCK     对于socket文件
       
    3. I/O多路复用
       同时监控多个文件,如果都未就绪,就可以休眠等待,避免CPU利用率过高 
#ifndef	__TFTP_H__
#define __TFTP_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <dirent.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define	LEN_PACKET_LEN		10
#define LEN_PACKET_FUNC		1
#define LEN_PACKET_HEAD   (LEN_PACKET_LEN + LEN_PACKET_FUNC)

#define LEN_FILE_FLAGS		1
#define LEN_FILE_NAME			20

enum {
	FUNC_START = 0,
	FUNC_LIST,
	FUNC_GET,
	FUNC_PUT,
	FUNC_END
};

int recv_fix_len(int sockfd, char *buf, int len);
int send_fix_len(int sockfd, const char *buf, int len);

void packet_set_len(char *packet, int len);
int packet_get_len(const char *packet);
void packet_set_func(char *packet, int func);
int packet_get_func(const char *packet);
int packet_get_func(const char *packet);
int packet_recv_proc(int sockfd);
int client_exe_list(int sockfd);
int server_exe_list(int sockfd);

#endif // __TFTP_H__

/*
 * 实现目标:
 * tftp协议
 *
 * 实现步骤:
 * 1. 制定协议
 *    客户端--->服务端
 *    +----------------+------------+--------------------------+-------+
 *    |  包体长度(10B) | 功能号(1B) |            包体          |  解释 |
 *    +----------------+------------+--------------------------+-------+
 *    |                |    '1'     |            无            |  list |
 *    +----------------+------------+--------------------------+-------+
 *    |                |    '2'     |        文件名            |  get  |
 *    +----------------+------------+--------+-----------------+-------+
 *    |                |    '3'     | 文件名 | 文件内容        |  put  |
 *    +----------------+------------+--------+-----------------+-------+
 *
 *    服务端--->客户端
 *    +----------------+------------+--------------------------+-------+
 *    |  包体长度(10B) | 功能号(1B) |            包体          |  解释 |
 *    +----------------+------------+--------------------------+-------+
 *    |                |    '1'     |       文件列表           |  list |
 *    +----------------+------------+----------+---------------+-------+
 *    |                |    '2'     | 文件状态 | 文件内容      |  get  |
 *    +----------------+------------+----------+---------------+-------+
 *    |                |    '3'     |      文件接收标志        |  put  |
 *    +----------------+------------+--------+-----------------+-------+
 *    1. 包体长度
 *       包体长度,用十进制字符串表示,比如: get数据包中的文件名是100字节 ---> "0000000100" --> 100
 *
 *    2. 文件名
 *       20个字节的字符串
 *
 *    3. 文件状态
 *       '0' - 文件不存在
 *       '1' - 文件存在
 *
 *    4. 文件接收标志
 *       '0' - 接收成功
 *       '1' - 失败
 *
 * 2. 包头处理
 *    2.1 打包/解包包体长度
 *    2.2 打包/解包功能号
 *    2.3 接收并解析包头
 *
 * 3. 协议解析框架  
 *
 * 4. list
 *
 * 5. get
 * 6. put
 *
 */
#include "tftp.h"

int recv_fix_len(int sockfd, char *buf, int len)
{
	int ret = 0;
	int recv_len = 0;
	
	while (recv_len < len){
		ret = recv(sockfd, buf + recv_len, len - recv_len, 0);
		if (-1 == ret){
			perror("Fail to recv");
			return -1;
		}
		
		recv_len += ret;
	}
	
	return 0;
}

int send_fix_len(int sockfd, const char *buf, int len)
{
	int ret = 0;
	int send_len = 0;
	
	while (send_len < len){
		ret = send(sockfd, buf + send_len, len - send_len, 0);
		if (-1 == ret){
			perror("Fail to send");
			return -1;
		}
		
		send_len += ret;
	}
	
	return 0;
}

// 2.1 打包/解包包体长度
void packet_set_len(char *packet, int len)
{
	char buf[LEN_PACKET_LEN + 1];
	char fmt[32];
	
	// fmt = %010d
	// sprintf(fmt, %010d, ...)
	sprintf(fmt, "%%0%dd", LEN_PACKET_LEN);
	
	// sprintf(buf, "%100d", 100);
	// buf = "00000000100"
	sprintf(buf, fmt, len);
	memcpy(packet, buf, LEN_PACKET_LEN);
}

int packet_get_len(const char *packet)
{
	char buf[LEN_PACKET_LEN + 1];
	
	memcpy(buf, packet, LEN_PACKET_LEN);
	buf[LEN_PACKET_LEN] = '\0';
	
	return atoi(buf); 
}

// 2.2 打包/解包功能号
void packet_set_func(char *packet, int func)
{
	char buf[LEN_PACKET_FUNC + 1];
	char fmt[32];
	
	sprintf(fmt, "%%0%dd", LEN_PACKET_FUNC); 
	sprintf(buf, fmt, func);
	memcpy(packet + LEN_PACKET_LEN, buf, LEN_PACKET_FUNC);
}

int packet_get_func(const char *packet)
{
	char buf[LEN_PACKET_FUNC + 1];
	
	memcpy(buf, packet + LEN_PACKET_LEN, LEN_PACKET_FUNC);
	buf[LEN_PACKET_FUNC] = '\0';
	
	return atoi(buf); 
}

// 2.3 接收并解析包头
int packet_recv_head(int sockfd, int *func)
{
	int len = 0;
	char packet_head[LEN_PACKET_LEN + LEN_PACKET_FUNC];
	
	// 接收包头
	len = recv_fix_len(sockfd, packet_head, sizeof(packet_head));
	if (-1 == len){
		return -1;
	}
	
	// 解析包头
	len = packet_get_len(packet_head);
	*func = packet_get_func(packet_head);
	if (*func <= FUNC_START || *func >= FUNC_END){
		return -1;
	}
	
	return len;
}

// 3. 协议解析框架  
int packet_recv_proc(int sockfd)
{
	int ret = 0;
	int len;
	int func;
	
	len = packet_recv_head(sockfd, &func);
	if (-1 == len){
		return -1;
	}
	
	printf("--------------------------------------------\n");
	printf("recv: func = %d, len = %d\n", func, len);
	printf("--------------------------------------------\n");
	
	switch (func){
		case FUNC_LIST:
			ret = server_exe_list(sockfd);
			break;
			
		case FUNC_GET:
			ret  = server_exe_get(sockfd, len);
			break;
			
		case FUNC_PUT:
			break;
			
		default:
			break;
	}
	
	return 0;
}

// 4. list
int client_exe_list(int sockfd)
{
	int ret = 0;
	int len;
	int func;
	char packet[1024];
	
	// 打包发送list协议
	packet_set_len(packet, 0);
	packet_set_func(packet, FUNC_LIST);
	
	ret = send_fix_len(sockfd, packet, LEN_PACKET_HEAD);
	if (-1 == ret){
		return -1;
	}
	
	// 接收服务的返回
	len = packet_recv_head(sockfd, &func);
	if (-1 == len){
		return -1;
	}
	
	// 接收文件列表
	ret = recv_fix_len(sockfd, packet, len);
	if (-1 == ret){
		return -1;
	}
	printf("%s\n", packet);

	return 0;
}

int server_exe_list(int sockfd)
{
	int ret = 0;
	int len = LEN_PACKET_HEAD;
	char packet[1024];
	DIR *pdir;
	struct dirent *pdirent;
	
	// 获取文件列表
	pdir = opendir(".");
	if (NULL == pdir){
		perror("Fail to opendir");
		return -1;
	}
	
	// 获取目录下的文件
	while ((pdirent = readdir(pdir)) != NULL) {
		
		// 隐藏文件
		if (pdirent->d_name[0] == '.'){
			continue;
		}
		
		// tftp.c tftp.h
		// "tftp.c tftp.h "
		sprintf(packet + len, "%s ", pdirent->d_name);
		len += strlen(packet + len);
	}
	
	// 设置包头
	packet_set_len(packet, len - LEN_PACKET_HEAD);
	packet_set_func(packet, FUNC_LIST);
	
	// 发送
	ret = send_fix_len(sockfd, packet, len);
	
	return ret; 
}

// get命令客户端
int client_exe_get(int sockfd, const char *name)
{
	int ret = 0;
	int file_len = 0;
	int recv_len = 0;
	int fd;
	char packet[1024];
	
	// 打包发送get命令请求
	packet_set_len(packet, strlen(name));
	packet_set_func(packet, FUNC_GET);
	memcpy(packet + LEN_PACKET_HEAD, name, strlen(name));
	
	ret = send_fix_len(sockfd, packet, LEN_PACKET_HEAD + strlen(name));
	if (-1 == ret){
		return -1;
	}
	
	// 接收解析服务器的返回包
	ret = recv_fix_len(sockfd, packet, LEN_PACKET_HEAD + LEN_FILE_FLAGS);
	if (-1 == ret){
		return -1;
	}
	
	if (packet[LEN_PACKET_HEAD] != '0'){
		// 失败
		printf("Get file is not exist.\n");
		return 0;
	}
	
	// 成功
	file_len = packet_get_len(packet) - LEN_FILE_FLAGS;
	
	// 接收保存文件的内容
	
	// 创建并打开文件
	fd = open(name, O_CREAT | O_WRONLY | O_TRUNC, 0666);
	if (-1 == fd){
		perror("Fail to open.");
		return -1;
	}
	
	// 接收并写入文件
	recv_len = 0;
	while (recv_len < file_len){
		ret = recv(sockfd, packet, sizeof(packet));
		if (-1 == ret){
			break;
		}
		
		write(fd, packet, ret);
		
		recv_len += ret;
	}
	close(fd);
	printf("recv %s successfully.\n", name);
	
	
}

// get服务端
// len 包体长度
int server_exe_get(int sockfd, int len)
{
	int ret = 0;
	int fd;
	int file_flag = 0;
	int file_len;
	int send_len;
	char name[LEN_FILE_NAME + 1];
	char packet[1024];
	char buf[32];
	
	// 接收文件名
	ret = recv_fix_len(sockfd, name, len);
	if (-1 == ret){
		return -1;
	}
	name[len] = '\0';
	
	// 打开文件
	fd = open(name, O_RDONLY);
	if (-1 == fd){
		perror("Fail to open.");
		file_flag = 1;
	}
	
	// 读取文件长度
	file_len = lseek(fd, 0, SEEK_END);
	
	// 打包发送包头及文件标志
	packet_set_len(packet, file_len + LEN_FILE_FLAGS);
	packet_set_func(packet, FUNC_GET);
	
	// 支持扩展
	sprintf(buf, "%d", file_flag);
	memcpy(packet + LEN_PACKET_HEAD, buf, LEN_FILE_FLAGS);
	
	// 不支持扩展
	// packet[LEN_PACKET_HEAD] = file_flag + '0';
	ret = send_fix_len(sockfd, packet, LEN_PACKET_HEAD + LEN_FILE_FLAGS);
	if (-1 == ret){
		return -1;
	}
	
	// 打包发送文件内容
	lseek(fd, 0, SEEK_SET);
	
	send_len = 0;
	while (send_len < file_len){
		ret = read(fd, packet, sizeof(packet));
		if (-1 == ret){
			perror("Fail to read.");
			return -1;
		}
		send_len += ret;
		
		ret = send_fix_len(sockfd, packet, ret);
		if (-1 == ret){
			return -1;
		}
	}
	
	printf("send successfully.\n");
}

/*
 * 实现目标:
 * 文件服务器
 * list
 * get
 * put
 * 
 * 实现步骤:
 * 1. 多个客户端并发响应
 *  1.1 线程回调函数
 *    1.2 创建线程
 * 
 * 2. tftp协议
 *    见《tftp.c》
 * 
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

// bzero
#include <strings.h>

// net
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "tftp.h"

// 1.1 线程回调函数
void *do_client(void *arg)
{
	int clientfd = (int)(long int)arg;
	
	packet_recv_proc(clientfd);
	
	close(clientfd);
	
	return NULL;
}

// ./server 192.168.0.249 8888
int main(int argc, const char *argv[])
{
	int sockfd;
	int clientfd;
	int ret;
	pthread_t tid;
	struct sockaddr_in server_addr;
	struct sockaddr_in peer_addr;
	socklen_t addrlen = sizeof(peer_addr);
	
	if (argc < 3){
		fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
		exit(EXIT_FAILURE);
	}
	
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd){
		perror("Fail to socket.");
		exit(EXIT_FAILURE);
	}

	bzero(&server_addr, sizeof(server_addr));	
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);
	ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if (-1 == ret){
		perror("Fail to bind.");
		exit(EXIT_FAILURE);
	}
	
	ret = listen(sockfd, 10);
	if (-1 == ret){
		perror("Fail to listen.");
		exit(EXIT_FAILURE);
	}
	
	while (1){
		clientfd = accept(sockfd, (struct sockaddr *)&peer_addr, &addrlen);
		if (-1 == clientfd){
			perror("Fail to accept.");
			break;
		}
		
		printf("------------------------------------------\n");
		printf("ip   : %s\n", inet_ntoa(peer_addr.sin_addr));
		printf("port : %d\n", ntohs(peer_addr.sin_port));
		printf("------------------------------------------\n");
		
		// 1.2 创建线程
		ret = pthread_create(&tid, NULL, do_client, (void *)(long int)clientfd);
		if (ret < 0){
			fprintf(stderr, "Fail to pthread_create: %d\n", ret);
			close(clientfd);
			break;
		}
		
		// 设置为分离线程
		pthread_detach(tid);
	}
	
	close(sockfd);
	
	return 0;
}

     
[2] noblock
    向文件的操作标志位中加入O_NONBLOCK,方法如下:
    1. open
       fd = open("test", ... | O_NONBLOCK);
       
    2. fcntl
       /*
        * @brief 获取/设置文件标志
        * @param[in] fd 文件描述符
        * @param[in] cmd
        *                     @li F_GETFL(void) 获取文件标志
        * @li F_SETFL(int) 设置文件标志
        * @return 失败返回-1 错误码见errno
        * 成功返回:
        *                     @li F_GETFL(void)     文件标志
        * @li F_SETFL(int) 0
        * @notes ... 不表示不定参数
        */
       int fcntl(int fd, int cmd, ... /* arg */ );
       
       例:
       int oflags;
       
       oflags = fcntl(sockfd, F_GETFL);
oflags |= O_NONBLOCK;
    fcntl(sockfd, F_SETFL, oflags);
    
[3] I/O多路复用
    1. 头文件
       #include <sys/select.h>
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>
       
    2. 数据结构
       typedef struct {
        long int fds_bits[1024 / 32];
    } fd_set; 
     
struct timeval {
long     tv_sec;     /* seconds */
long    tv_usec;    /* microseconds */                                          
};
       
    2. 函数
       /*
        * @brief 监控多个文件(是否可读/可写/异常)
        * @param[in] nfds 最大的文件描述符 + 1
        * @param[in | out] readfds 要监控是否可读的文件描述符集
        * NULL 不监控文件的读
        * @param[in | out]  writefds 要监控是否可写的文件描述符集
        *                     NULL                  不监控文件的写
        * @param[in | out]  exceptfds 要监控是否异常的文件描述符集
        * NULL 不监控文件的异常
        * @param[in | out] timeout  超时值
        * NULL 永远等待
        * @return @li > 0 就绪的文件描述符个数
        * @li = 0 超时
        * @li -1 错误(见errno)
        * @notes 非就绪的文件的文件描述符从文件描述符集中删除
        *                     select会阻塞,直到有一个文件描述符就绪
        */
       int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    
       /*
        * @brief 监控(查询)文件是否可读、可写和异常
        * @param[in] nfds 最大的文件描述 + 1
        * @param[in|out] readfds 是否可读的文件描述符
        * @param[in|out] writefds 是否可读的文件描述符
        * @param[in|out] exceptfds 是否可读的文件描述符
        * @param[in|out] timeout 设置超时时间(NULL -- 永远阻塞), 输出超过就绪多长时间
        * @return @li > 0 就绪的文件描述符个数
        * @li 0 超时
        * @li = -1 发生错误(错误码见errno)
       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
                  
       /*
        * @brief 设置文件描述符到文件描述符集
        * @param[in] fd 文件描述符
        * @param[out] fds 文件描述符集
        */
       void FD_SET(int fd, fd_set *fds);
             
       /*
        * @brief 从文件描述符集中,清除文件描述符
        * @param[in] fd 文件描述符
        * @param[out] fds 文件描述符集
        */ 
       void FD_CLR(int fd, fd_set *fds);
              
       /*
        * @brief 判断文件描述符集中的文件描述符是否置位
        * @param[in] fd 文件描述符
        * @param[in] fds 文件描述符集
        * @return @li 1 文件描述符置位
        * @li 0 文件描述符没有置位
        */ 
       int FD_ISSET(int fd, fd_set *fds);
         
    /*
        * @brief 清空文件描述符集
        * @param[in] fd 文件描述符
        * @param[out] fds 文件描述符集
        */ 
       void FD_ZERO(fd_set *fds);

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// ./server 192.168.0.249 8888
int main(int argc, const char *argv[])
{
	int ret;
	int sockfd;
	struct sockaddr_in server_addr;
	char packet[1024];
	
	if (argc < 3){
		fprintf(stderr, "Usage: %s <server ip> <server port>\n", argv[0]);
		exit(EXIT_FAILURE);
	}
	
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd){
		perror("Fail to socket.");
		exit(EXIT_FAILURE);
	}
	
	bzero(&server_addr, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);
	
	ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if (-1 == ret){
		perror("Fail to connect.");
		close(sockfd);
		exit(EXIT_FAILURE);
	}

	while (1){
		putchar('>');
		fgets(packet, sizeof(packet), stdin);
		packet[strlen(packet) - 1] = '\0';
	

		ret = send(sockfd, packet, strlen(packet), 0);
		if (-1 == ret){
			perror("Fail to send.");
			close(sockfd);
			break;
		}
		
		if (strcmp(packet, "bye") == 0) break;
	}
	
	close(sockfd);
	
	return 0;
}

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <fcntl.h>

// ./server 192.168.0.249 8888
int main(int argc, const char *argv[])
{
	int ret;
	int sockfd;
	int clientfd = -1;
	struct sockaddr_in server_addr;
	struct sockaddr_in peer_addr;
	socklen_t addrlen = sizeof(peer_addr);
	char packet[1024];
	int oflags;
	
	if (argc < 3){
		fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
		exit(EXIT_FAILURE);
	}
	
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd){
		perror("Fail to socket.");
		exit(EXIT_FAILURE);
	}
	
	bzero(&server_addr, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);
	
	ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if (-1 == ret){
		perror("Fail to bind.");
		exit(EXIT_FAILURE);
	}
	
	ret = listen(sockfd, 10);
	if (-1 == ret){
		perror("Fail to listen.");
		exit(EXIT_FAILURE);
	}
	
	// 设置套接字为非阻塞
	oflags = fcntl(sockfd, F_GETFL);
	oflags |= O_NONBLOCK;
	fcntl(sockfd, F_SETFL, oflags);
	
	while (1) {
		ret = accept(sockfd, (struct sockaddr *)&peer_addr, &addrlen);
		if (-1 == ret && (errno != EAGAIN) && (errno != EWOULDBLOCK)){
			perror("Fail to accept.");
			break;
		} else if (ret >= 0) {
			clientfd = ret;
			
			// 设置套接字为非阻塞
			oflags = fcntl(clientfd, F_GETFL);
			oflags |= O_NONBLOCK;
			fcntl(clientfd, F_SETFL, oflags);
			
			printf("---------------------------------------\n");
			printf("ip   : %s\n", inet_ntoa(peer_addr.sin_addr));
			printf("port : %d\n", ntohs(peer_addr.sin_port));
			printf("---------------------------------------\n");
		}
	
		if (-1 == clientfd) continue;
			
		ret = recv(clientfd, packet, sizeof(packet), 0);
		if (-1 == ret && (errno != EAGAIN) && (errno != EWOULDBLOCK)){
			perror("Fail to recv.");
			close(clientfd);
			break;
		}
		packet[ret] = '\0';
		if (ret > 0){
		
			printf("------------------------\n");
			printf("recv : %s\n", packet);
			printf("------------------------\n");
		}
		
		if (strcmp(packet, "bye") == 0) break;
	}
	
	close(clientfd);
	close(sockfd);
	
	return 0;
}


[3] 服务器实现
    1. UDP
       轮训,并发不好实现
       
    2. TCP
       并发,编程简单
       轮训, 容易交换数据,但是需要用到非阻塞 + IO多路复用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// ./server 192.168.0.249 8888
int main(int argc, const char *argv[])
{
	int ret;
	int sockfd;
	struct sockaddr_in server_addr;
	char packet[1024];
	
	if (argc < 3){
		fprintf(stderr, "Usage: %s <server ip> <server port>\n", argv[0]);
		exit(EXIT_FAILURE);
	}
	
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd){
		perror("Fail to socket.");
		exit(EXIT_FAILURE);
	}
	
	bzero(&server_addr, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);
	
	ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if (-1 == ret){
		perror("Fail to connect.");
		close(sockfd);
		exit(EXIT_FAILURE);
	}

	while (1){
		putchar('>');
		fgets(packet, sizeof(packet), stdin);
		packet[strlen(packet) - 1] = '\0';
	

		ret = send(sockfd, packet, strlen(packet), 0);
		if (-1 == ret){
			perror("Fail to send.");
			close(sockfd);
			break;
		}
		
		if (strcmp(packet, "bye") == 0) break;
	}
	
	close(sockfd);
	
	return 0;
}

/*
 * 实现目标:
 * 使用轮训实现TCP服务器
 *
 * 实现步骤:
 * 1. 直接使用阻塞套接字(不行)
 * 2. 修改为非阻塞套接字(一个客户端套接字和监听套接字)   CPU利用率太高,一直在主循环中执行,即使所以客户端及监听套接字空闲
 * 3. I/O多路复用(同时监听多个文件,只要有一个就绪,处理改文件,所有非就绪,可以休眠)
 *    select
 *    3.1 创建文件描述集
 *    3.2 添加所有有监控的文件描述符到文件描述符集
 *    3.3 利用select监控文件是否就绪
 *        对就绪的文件       处理
 *        没有就绪的文件     休眠
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <fcntl.h>

// ./server 192.168.0.249 8888
int main(int argc, const char *argv[])
{
	int ret;
	int sockfd;
	int clientfd = -1;
	struct sockaddr_in server_addr;
	struct sockaddr_in peer_addr;
	socklen_t addrlen = sizeof(peer_addr);
	char packet[1024];
	int oflags;
	int maxfd;
	int i;
	struct timeval timeout = {10, 0};
	
	// 3.1 创建文件描述集
	fd_set readfds;
	fd_set bak_readfds;
	
	FD_ZERO(&readfds);
	FD_ZERO(&bak_readfds);
	
	if (argc < 3){
		fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
		exit(EXIT_FAILURE);
	}
	
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd){
		perror("Fail to socket.");
		exit(EXIT_FAILURE);
	}
	
	bzero(&server_addr, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);
	
	ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if (-1 == ret){
		perror("Fail to bind.");
		exit(EXIT_FAILURE);
	}
	
	ret = listen(sockfd, 10);
	if (-1 == ret){
		perror("Fail to listen.");
		exit(EXIT_FAILURE);
	}
	
	// 设置套接字为非阻塞
	oflags = fcntl(sockfd, F_GETFL);
	oflags |= O_NONBLOCK;
	fcntl(sockfd, F_SETFL, oflags);
	
	// 3.2 添加监听的文件描述符到文件描述符集
	FD_SET(sockfd, &bak_readfds);
	maxfd = sockfd;
	
	while (1) {
		
		// 定时10S
		timeout.tv_sec = 10;
		timeout.tv_usec = 0;
		
		readfds = bak_readfds;
		
		ret = select(maxfd + 1, &readfds, NULL, NULL, &timeout);
		if (-1 == ret){
			break;
		} else if (0 == ret) {
			printf("select timeout\n");
			continue;
		}
		
		for (i = 0; i <= maxfd; i++){
			if (!FD_ISSET(i, &readfds)) {
				continue;
			}
			
			if (i == sockfd) {
				ret = accept(sockfd, (struct sockaddr *)&peer_addr, &addrlen);
				if (-1 == ret && (errno != EAGAIN) && (errno != EWOULDBLOCK)){
					perror("Fail to accept.");
					break;
				} else if (ret >= 0) {
					clientfd = ret;
					
					// 设置套接字为非阻塞
					oflags = fcntl(clientfd, F_GETFL);
					oflags |= O_NONBLOCK;
					fcntl(clientfd, F_SETFL, oflags);
					
					// 添加客户端文件描述符
					FD_SET(clientfd, &bak_readfds);
					if (maxfd < clientfd) {
						maxfd = clientfd;
					}
					
					printf("---------------------------------------\n");
					printf("ip   : %s\n", inet_ntoa(peer_addr.sin_addr));
					printf("port : %d\n", ntohs(peer_addr.sin_port));
					printf("---------------------------------------\n");
				}
				
				continue;
			}
				
			ret = recv(i, packet, sizeof(packet), 0);
			if (-1 == ret && (errno != EAGAIN) && (errno != EWOULDBLOCK)){
				perror("Fail to recv.");
				close(clientfd);
				break;
			}
			packet[ret] = '\0';
			if (ret > 0){
			
				printf("------------------------\n");
				printf("recv : %s\n", packet);
				printf("------------------------\n");
			}
		}
		
		if (strcmp(packet, "bye") == 0) break;
	}
	
	close(clientfd);
	close(sockfd);
	
	return 0;
}

       
[4] 网络调试工具
1. telnet
   测试服务端是否可连,命令如下:
   $ telnet <IP> <端口号>
   
2. lsof
   查看套接字信息:
   $ lsof -i [udp | tcp] [:port]
   $ lsof -i 列出所有的套接字信息
   $ lsof -i tcp 列出使用tcp协议的套接字信息
   $ lsof -i udp 列出使用tcp协议的套接字信息
   $ lsof -i :<port>        列出绑定port端口的套接字信息
   
3. netstat
   显示网络状态:
   $ netstat -napt
   $ netstat -napu
   
   -n 显示ip地址代替机器名
   -a 显示所有的套接字信息
   -p 显示使用套接字的进程的ID和程序名
   -t 显示使用tcp协议的套接字
   -u 显示使用udp协议的套接字
   
4. tcpdump
   linux系统下的抓包工具,常用命令形式:
   $ sudo tcpdump -i eth0 -n 'port 23 and src host 192.168.1.100' -w log.pcap
   
   sudo root权限运行
   -i eth0 指定抓哪个网络接口上的包
   -n 源地址和目标地址显示ip地址,而不是主机名
   'port 23 and src host 192.168.1.100' 监控端口为23,并且源主机地址为192.168.1.100的包
   -w log.pcap      详细协议内容记录到log.pcap,该文wireshark可以直接打开
   
5. wireshark
   见《wireshark抓包教程.doc》
    
[5] 网络协议分析
    TCP协议: 分组
    IP协议: 分片
    分组和分片的目的是,数据链路层的包不会大于网络的MTU
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值