并发服务器模型的实现主要有三种方法:
fcntl
将 sockfd 设置为非阻塞模式:修改tcp_net_server.c
为 tcp_net_server_multiprocess
的代码如下:
#include "tcp_net_socket.h"
int main(int argc, char * argv[])
{
if(argc < 3) {
printf("usage:./servertcp ip port\n");
exit(-1);
}
// 注册信号
signalhandler();
// 初始化socket
int sfd = tcp_init(argv[1], atoi(argv[2]));
// 用while循环表示可以与多个客户端接收和发送,但仍然是阻塞式的
while(1) {
// 接收客户端的连接
int cfd = tcp_accept(sfd); // 如果没有连接请求阻塞
char buf[512] = {0};
if(0 == fork()) { // 开辟一个新进程出来请求
// 与客户端进行通信
if(-1 == recv(cfd, buf, sizeof(buf), 0)) {
perror("recv");
close(cfd);
}
else {
printf("%s: %d: 正在处理...\n", __FILE__, __LINE__);
puts(buf); usleep(5000000);
printf("%s: %d: 处理完成...\n", __FILE__, __LINE__);
// 发送数据
if(-1 == send(cfd, "hello, world", 12, 0))
perror("send");
close(cfd);
}
}
else close(cfd);
}
close(sfd);
return 0;
}
读大量文件:
比较高效的方法,每次读取指定大小的数据(除非读取结束),忽略中断的影响。下述方法仍然不够高效,因为要不断的进行系统调用。
ssize_t readn(int fd, char *buf, int size)
{
/* 函数意义:读取大量文件
* 参数意义:
* - int fd:文件描述符;
* - char *buf:存储数据的缓存空间;
* - int size:缓存空间 *buf 的大小
* 返回值:
* - 如果读取成功,返回实际的数据大小;
* - 如果读取失败,返回 -1;
*/
char * pbuf = buf;
int total, nread;
for(total = 0; total < size; ) {
nread = read(fd, pbuf, size - total);
if (0 == nread) return total;
else if (-1 == nread) {
if (EINTR == errno) continue; // 忽略中断的影响
else return -1;
}
else {
total += nread;
pbuf += nread;
}
}
return total;
}
写大量文件:
比较高效的方法,每次写入指定大小的数据,忽略中断的影响。下述方法仍然不够高效,因为要不断的进行系统调用;
ssize_t writen(int fd, char * buf, int size)
{
/* 函数意义:写入大量文件
* 参数意义:
* - int fd:文件描述符;
* - char *buf:存储数据的缓存空间;
* - int size:缓存空间 *buf 的大小
* 返回值:
* - 如果写入成功,返回实际写入数据的大小;
* - 如果写入失败,返回 -1;
*/
char * pbuf = buf;
int total, nwrite;
for(total = 0; total < size; ) {
nwrite = write(fd, pbuf, size - total);
if (-1 == nwrite) {
if (EINTR == errno) continue; // 忽略中断的影响
else return -1;
}
total += nwrite;
pbuf += nwrite;
}
return total;
}
实现一个功能:将服务器上的文件的内容全部发给客户端。
服务的实现:服务器名称为:tcp_net_server_multithread.c
;
注意:下述代码并没有进行线程相关资源的销毁工作。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEAFUALT_SVR_PORT 2828
#define FILE_MAX_LEN 64
char filename[FILE_MAX_LEN];
void printPerror(const char * str)
{
printf("%s: %d: ", __FILE__, __LINE__);
perror(str);
}
// 线程回调函数
static void * handle_client(void * arg)
{
/* 函数意义:线程的回调函数
* 参数意义:
* - void *arg:Socket 的描述符,需要进行强制类型转化;
* 返回值:
* - 无返回值;
*/
int sock = *(int*)arg;
char buff[1024];
int len;
printf("begin send\n");
FILE * file = fopen(filename, "r");
if(!file) {
close(sock);
exit(1);
}
// 发送文件名
if(-1 == send(sock, filename, FILE_MAX_LEN, 0)) {
perror("send file name\n");
goto EXIT_THREAD;
}
printf("begin send file %s ...\n", filename);
while(!feof(file)) {
len = fread(buff, 1, sizeof(buff), file);
printf("%s: %d: server read %s, len %d\n", __FILE__, __LINE__, filename, len);
if(send(sock, buff, len, 0) < 0) {
printPerror("send file: ");
goto EXIT_THREAD;
}
}
EXIT_THREAD:
if(file) fclose(file);
close(sock);
}
int main(int argc, char *argv[])
{
int sockfd, new_fd;
// 申请两个 ipv4的地址
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
int sin_size, numbytes;
pthread_t cli_thread; // 线程句柄
unsigned short port;
if(argc < 2) {
printf("need a filename without path\n");
exit(1);
}
strncpy(filename, argv[1], FILE_MAX_LEN);
port = DEAFUALT_SVR_PORT;
if(argc > 3) port = (unsigned short)atoi(argv[2]);
// 第一步:建立TCP套接字Socket
if(-1 == (sockfd = socket(AF_INET, SOCK_STREAM, 0))) {
printPerror("socket");
exit(-1);
}
// 第二步:设置侦听端口
memset(&my_addr, 0, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(port);
my_addr.sin_addr.s_addr = INADDR_ANY; // 任意ip地址可以通信
// 第三步:绑定套接字,把Socket队列与端口关联起来
if(-1 == bind(sockfd, (struct sockaddr * )&my_addr, sizeof(struct sockaddr))) {
printPerror("bind");
goto EXIT_MAIN;
}
// 第四步:开始在端口上帧听,是否有客户端发来连接
if(-1 == listen(sockfd, 10)) {
printPerror("listen");
goto EXIT_MAIN;
}
printf("#@ listen port %d\n", port);
// 第五步:循环与客户端通信
while(1) {
printf("%s: %d: server waitng ...\n", __FILE__, __LINE__);
sin_size = sizeof(struct sockaddr_in);
// 如果有客户端建立连接,将产生一个全新的套接字new_fd, 用于专门跟这些客户通信
if((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
printPerror("accept:");
goto EXIT_MAIN;
}
printf("---client (ip=%s:port=%d) request \n",inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port));
// 生成一个线程完成和客户端会话, 父进程继续监听
pthread_create(&cli_thread, NULL, handle_client, (void *)&new_fd);
}
// 第六步:关闭Socket
EXIT_MAIN:
close(sockfd);
return 0;
}
客户端实现:文件名为tcp_net_client_multithread.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEAFUALT_SVR_PORT 2828
#define FILE_MAX_LEN 64
void printPerror(const char * str)
{
printf("%s: %d: ", __FILE__, __LINE__);
perror(str);
}
int main(int argc, char *argv[])
{
int sockfd, numbytes;
char buf[1024], filename[FILE_MAX_LEN];
char ip_addr[64];
struct hostent * he;
struct sockaddr_in their_addr;
int i = 0, len, total;
unsigned short port;
FILE * file = NULL;
if(argc < 2) {
printf("need a server ip \n");
exit(1);
}
strncpy(ip_addr, argv[1], sizeof(ip_addr));
port = DEAFUALT_SVR_PORT;
if(argc > 3) port = (unsigned short)atoi(argv[2]);
// 域名解析
// he = gethostbyname(argv[1])
// 第一步:建立TCP套接字Socket
if(-1 == (sockfd = socket(AF_INET, SOCK_STREAM, 0)))
{
printPerror("socket");
exit(-1);
}
// 第二步:设服务器地址和端口
memset(&their_addr, 0, sizeof(their_addr));
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(port);
their_addr.sin_addr.s_addr = inet_addr(ip_addr);
// their_addr.sin_addr = *((struct in_addr *)he->h_addr);
// bzero(&(their_addr.sin_zero), 8)
// 第三步:用connect和服务器建立连接,这里没有使用本地端口,由协议栈自动分配端口
printf("%s: %d: connect server %s: %d\n", __FILE__, __LINE__, ip_addr, port);
if(-1 == connect(sockfd, (struct sockaddr * )&their_addr, sizeof(struct sockaddr))) {
printPerror("connet");
exit(1);
}
// 发生数据
if(send(sockfd, "hello", 6, 0) < 0) {
printPerror("send ");
exit(1);
}
// 接收文件名,为编程方便,假设前64字节固定是文件名,不足用0来填充
total = 0;
while(total < FILE_MAX_LEN) {
// 注意这里的接收buffer长度,始终是未接收文件名剩下长度
len = recv(sockfd, filename+total, (FILE_MAX_LEN - total), 0);
if (len <= 0) break;
total += len;
}
// 接收文件名出错
if(total != FILE_MAX_LEN) {
printPerror("failure file name");
exit(-3);
}
printf("%s: %d: recv file %s ...\n", __FILE__, __LINE__, filename);
//file = fopen(filename, "wb");
file = fopen("./abc.txt", "wb");
if(!file) {
printf("create file %s failure", filename);
perror("create:");
exit(-3);
}
// 接收文件数据
printf("%s: %d: recv begin\n", __FILE__, __LINE__);
total = 0;
while(1) {
len = recv(sockfd, buf, sizeof(buf), 0);
if (len == -1) break;
total += len;
fwrite(buf, 1, len, file);
}
fclose(file);
printf("%s: %d: recv file %s success total length %d\n", __FILE__, __LINE__, filename, total);
close(sockfd);
return 0;
}
#include
#include
......
// 建立 Socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
...
int iflags;
if((iflags = fcntl(sfd, F_GETFL, 0)) < 0 ) { // 获取 sfd 的模式信息
printf("%s: %d: ", __FILE__, __LINE__);
perror("fcntl F_GETFL");
}
iflags |= O_NONBLOCK; // 设置为非阻塞模式
// iflags |= O_ASYNC;
if(fcntl(sfd, F_SETFL, iflags) < 0) { // 写入修改后的模式信息
printf("%s: %d: ", __FILE__, __LINE__);
perror("fcntl");
}
通过调用 fcntl
将 sockfd
设置为非阻塞模式,在遇到多客户端请求时,会有什么表现?
服务器在执行到函数 accept()
时,会立即返回:accept: Resource temporarily unavailable
;
sockfd
为阻塞模式,在 accept
函数执行中会阻塞直到等到客户端的连接请求时返回;sockfd
设置为非阻塞模式时,accept
模式会立即返回,即使没有等到客户端的连接。这样就可以去处理其他任务;