server端未处理高并发请求一般採用例如如下方式:css
I/O
对象提供服务#include <unistd.h> pid_t fork(); // 成功返回进程 ID, 失败返回-1
fork
函数将建立调用的函数副本。子进程将使用新的内存空间复制当前函数的环境。linux
ID
可以理解为调用该函数以后将存在两个pid_t
,分别存在父子进程中,所以它们将会依据不一样的函数值运行对应的进程代码。编程
在调用函数前的所有变化都会在子进程中保持一致。数组
调用函数以后的变化将不会影响彼此,因为它们全然不相干。尽管可以经过进程间通讯交换信息。bash
向exit
函数传递的參数值和return
语句返回的值会传递给操做系统。markdown
操做系统不会销毁子进程。而是将这些值传递给产生该子进程的父进程。处在这样的状态下的进程就是僵尸进程。多线程
exit(0)
表示程序正常, exit(1)/exit(-1)
表示程序异常退出;
exit()
结束当前进程/当前程序/。在整个程序中,仅仅要调用 exit
,就结束.并发
exit(0)
:正常运行程序并退出程序;
exit(1)
:非正常运行致使退出程序;
return()
:返回函数。若在main
主函数中,则会退出函数并返回一值。可以写为return(0)
。或return 0
。socket
exit
表示进程终结,不管是在哪一个函数调用中。即便还存在被调函数。
return
表示函数返回,假设是在主函数中意味着进程的终结。假设不是在主函数中那么会返回到上一层函数调用。tcp
具体说:
return
返回函数值。是关键字;exit
是一个函数。return
是语言级别的。它表示了调用堆栈的返回;而exit
是系统调用级别的,它表示了一个进程的结束。return
是函数的退出(返回);exit
是进程的退出。return
是C
语言提供的,exit
是操做系统提供的(或者函数库中给出的)。return
用于结束一个函数的运行,将函数的运行信息传出给其它调用函数使用;exit
函数是退出应用程序,删除进程使用的内存空间。并将应用程序的一个状态返回给OS
,这个状态标识了应用程序的一些运行信息,这个信息和机器和操做系统有关。一般是 0
为正常退出。非0
为非正常退出。return
和exit
效果很是明显。但是在main
函数中调用return
和exit
的现象就很是模糊,多数状况下现象都是一致的。#include <sys/wait.h>
pid_t wait(int * statloc); // 成功返回终止的子进程 ID。失败返回 -1
当有子进程终止时,子进程终止时传递的返回值将保存在该函数參数所指内存空间,參数指向的单元中还包括其它信息。需要使用宏进行分离。
经过调用该函数以前终止的子进程相关信息将保存在參数变量中,同一时候。相关子进程被全然销毁。调用是假设没有已终止的进程,那么程序将会堵塞直到有子进程终止。
WIFEXITED
: 子进程正常终止时返回真WEXITSTATUS
: 返回子进程的返回值也就是说。向wait
函数传递变量status
的地址时,调用wait
函数后应编写例如如下代码:
if(WIFWXITED(status))
{
puts("Normal termination!");
printf("Child pass num: %d", WEXITSTATUS(status));
}
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int * statloc, int options); // 同上
~ pid: 等待终止的目标子进程的ID, 若传递-1,则等效于 wait, 可以等待随意子进程终止
~ statloc: 同上
~ options: 传递常量 WNOHANG, 即便没有终止的子进程也不会进入堵塞状态,而是返回0并退出
代码演示样例:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int status;
pid_t pid = fork();
if(pid == 0)
{
sleep(15);
return 34;
}
else
{
while(!waitpid(-1, &status, WNOHANG)) //假设没有进程终止,就循环等待
{
sleep(3);
puts("sleep 3 sec.");
}
if(WIFEXITED(status))
printf("Child send %d \n", WEXITSTATUS(status));
}
return 0;
}
子进程终止的识别主题是操做系统,所以在子进程终止的时候由操做系统将这些信息通知忙碌的父进程,父进程停下手上的工做处理相关事宜。
为此,咱们引入信号处理。此处的“信号”是指在特定时间发生时由操做系统向进程发送的消息。为了响应该消息,运行与消息相关的本身定义操做的过程称为“处理”或“信号处理”。
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int); // 在产生信号时调用。返回以前注冊的函数指针
发生第一个參数表明的状况时。调用第二个參数所指的状况。
第一个參数可能对应的常数:
SIGALRM
: 已到经过alarm
函数注冊的时间SIGINT
: 输入CTRL + C
SIGCHLD
: 子进程终止代码演示样例:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void timeout(int sig) // 信号处理器
{
if(sig == SIGALRM)
puts("Time out!");
alarm(2);
}
void keycontrol(int sig)
{
if(sig == SIGINT)
puts("Ctrl + C Pressed");
}
int main()
{
int i;
signal(SIGALRM, timeout); // 注冊处理函数
signal(SIGINT, keycontrol);
alarm(2); // unistd.h
for(i=0; i < 3; i++)
{
puts("wait ... ");
sleep(100);
}
return 0;
}
#include <signal.h>
int sigaction(int signo, const struct sigaction * act, struct sigaction * oldact); // 成功返回0, 失败返回-1
~ signo: 传递信号信息
~ act: 对应于第一个參数的信号处理函数信息
~ oldact: 经过此參数获取以前注冊注冊的信号处理函数指针。不需要则传递0
struct sigaction
{
void (*sa_handler)(int); // 信号处理的函数指针
sigset_s sa_mask; // 用于指定信号相关的选项和特性
int sa_flags;
}
使用上和以前的signal
没有明显差异。
struct sigaction act;
act.sa_handler=timeout;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
sigaction(SIGALRM, &act, 0);
...
代码演示样例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void read_childproc(int sig)
{
int status;
pid_t id = waitpid(-1, &status, WNOHANG);
if(WIFEXITED(status))
{
printf("Remove proc id : %d \n", id);
printf("Child send : %d \n", WEXITSTATUS(status));
}
}
int main(int argc, char * argv[])
{
pid_t pid;
struct sigaction act;
act.sa_handler = read_childproc;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD, &act, 0);
pid = fork();
if(pid == 0)
{
puts("Hi! I'am child proc");
sleep(10);
return 12;
}
else
{
printf("Child proc id : %d \n", pid);
pid = fork();
if(pid == 0)
{
puts("Hi! I'am child proc");
sleep(10);
exit(24);
}
else
{
int i;
printf("Child proc id : %d \n", pid);
for(i=0; i < 5; i++)
{
puts("wait ... ");
sleep(5);
}
}
}
return 0;
}
为了等待SIGCHLD
信号,父进程共暂停5次,每次间隔5秒。
发生信号时。父进程将被唤醒,所以实际暂停不到25秒。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void errorhandling(char *message);
void read_childproc(int sig);
int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
struct sockaddr_in serv_adr, clnt_adr;
pid_t pid;
struct sigaction act;
socklen_t adr_sz;
int str_len, state;
char buf[BUF_SIZE];
if(argc != 2)
{
printf("Usage : %s <port> \n", argv[0]);
exit(1);
}
act.sa_handler = read_childproc;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
state = sigaction(SIGCHLD, &act, 0);
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
errorhandling("bind() error");
puts("bind sucess");
if(listen(serv_sock, 5) == -1)
errorhandling("listen() error");
puts("listen success");
while(1)
{
adr_sz = sizeof(clnt_adr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
if(clnt_sock == -1)
continue;
else
puts("new client connected ..");
pid = fork();
if(pid == -1)
{
puts("pid = -1");
close(clnt_sock);
continue;
}
if(pid == 0)
{
puts("child proc.");
close(serv_sock);
while((str_len = read(clnt_sock, buf, BUF_SIZE)) != 0)
write(clnt_sock, buf, str_len);
close(clnt_sock);
puts("client disconnected ..");
return 0;
}
else
close(clnt_sock);
}
close(serv_sock);
return 0;
}
void read_childproc(int sig)
{
pid_t pid;
int status;
pid = waitpid(-1, &status, WNOHANG);
printf("remove proc id : %d \n", pid);
}
void errorhandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
上述演示样例中父进程将两个文件描写叙述符(server套接字和客户端套接字)复制给子进程。
实际上仅仅是复制了文件描写叙述符,没有复制套接字。
因为套接字并非进程所有—严格来讲,套接字属于操做系统—仅仅是进程拥有表明对应套接字的文件描写叙述符。
如上图所看到的,仅仅有两个文件描写叙述符都终止后才干销毁套接字。即便子进程销毁了与客户端的套接字文件描写叙述符也不能全然销毁套接字。所以,调用fork
函数以后,要将无关的套接字文件描写叙述符关掉。例如如下图所看到的:
切割模型例如如下:
在客户端中将读写分离。这样就不用再写以前等待读操做的完毕。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char * message);
void read_routine(int sock, char *message);
void write_routine(int sock, char *message);
int main(int argc, char *argv[])
{
int sock;
pid_t pid;
char buf[BUF_SIZE];
struct sockaddr_in serv_adr;
if(argc != 3)
{
printf("Usage : %s <IP> <port> \n", argv[0]);
exit(1);
}
sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
serv_adr.sin_port = htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
error_handling("connect() error ");
pid = fork();
if(pid == 0)
write_routine(sock, buf);
else
read_routine(sock, buf);
close(sock);
return 0;
}
void read_routine(int sock, char *buf)
{
while(1)
{
int str_len = read(sock, buf, BUF_SIZE);
if(str_len == 0)
return ;
buf[str_len] = 0;
printf("Message from server : %s", buf);
}
}
void write_routine(int sock, char *buf)
{
while(1)
{
fgets(buf, BUF_SIZE, stdin);
if(!strcmp(buf, "Q\n"))
{
shutdown(sock, SHUT_WR);
return;
}
write(sock, buf, strlen(buf));
}
}
void error_handling(char * message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
管道属于操做系统资源,所以。两个进程经过操做系统提供的内存空间进行通讯。如下是建立管道的函数:
#include <unistd.h>
int pipe(int filedes[2]) // 成功返回0, 失败返回-1
~ filedes[0]: 经过管道接收数据时使用的文件描写叙述符
~ filedes[1]: 经过管道发送数据时使用的文件描写叙述符
演示样例代码:
#include <stdio.h>
#include <unistd.h>
#define BUF_SIZE 30
int main(int argc, char *argv[])
{
int fds[2];
char str[] = "Who are you?";
char buf[BUF_SIZE];
pid_t pid;
pipe(fds);
printf("fds[0] : %d, fsds1[1] : %d\n", fds[0], fds[1]);
pid = fork();
if(pid == 0)
{
printf("child proc : %d \n", pid);
read(fds[0], buf, BUF_SIZE);
puts(buf);
}
else
{
printf("parent proc : %d \n", pid);
write(fds[1], str, sizeof(str));
}
return 0;
}
上述代码的示比例如如下:
模型例如如下:
这里有一个问题,“向管道中传递数据的时候。先读的进程会把数据读走”。
简而言之,数据进入到管道中就成为无主数据。不管谁先读取数据都可以将数据读走。
综上所述,使用一个管道实现双向通道并非易事。因为需要预測并控制通讯流,这是不现实的。所以可以使用两个管道实现双向通讯。模型例如如下:
#include <stdio.h>
#include <unistd.h>
#define BUF_SIZE 30
int main(int argc, char *argv[])
{
int fds1[2], fds2[2];
char str1[] = "Who are you?"; char str2[] = "Thank you!"; char buf[BUF_SIZE]; pid_t pid; pipe(fds1); pipe(fds2); printf("fds1[0] : %d, fds1[1] : %d\n", fds1[0], fds1[1]); printf("fds2[0] : %d, fds2[1] : %d\n", fds2[0], fds2[1]); pid = fork(); if(pid == 0) { printf("child proc : %d \n", pid); write(fds1[1], str1, sizeof(str1)); read(fds2[0], buf, BUF_SIZE); printf("Child proc output: %s \n", buf); } else { printf("parent proc : %d \n", pid); read(fds1[0], buf, BUF_SIZE); printf("Parent proc output: %s \n", buf); write(fds2[1], str2, sizeof(str2)); sleep(3); } return 0; }
代码演示样例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void errorhandling(char *message);
void read_childproc(int sig);
int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
struct sockaddr_in serv_adr, clnt_adr;
pid_t pid;
int fds[2];
struct sigaction act;
socklen_t adr_sz;
int str_len, state;
char buf[BUF_SIZE];
if(argc != 2)
{
printf("Usage : %s <port> \n", argv[0]);
exit(1);
}
act.sa_handler = read_childproc;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
state = sigaction(SIGCHLD, &act, 0);
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
errorhandling("bind() error");
puts("bind sucess");
if(listen(serv_sock, 5) == -1)
errorhandling("listen() error");
pipe(fds);
pid = fork();
if(pid == 0)
{
FILE * fp = fopen("echomsg.txt", "wt");
char msgbuf[BUF_SIZE];
int i, len;
for(i=0; i<10; i++)
{
len = read(fds[0], msgbuf, BUF_SIZE);
fwrite((void *)msgbuf, 1, len, fp);
}
fclose(fp);
return 0;
}
while(1)
{
adr_sz = sizeof(clnt_adr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
if(clnt_sock == -1)
continue;
else
puts("new client connected ..");
pid = fork();
if(pid == 0)
{
puts("child proc.");
close(serv_sock);
while((str_len = read(clnt_sock, buf, BUF_SIZE)) != 0)
{
write(clnt_sock, buf, str_len);
write(fds[1], buf, str_len);
}
close(clnt_sock);
puts("client disconnected ..");
return 0;
}
else
close(clnt_sock);
}
close(serv_sock);
return 0;
}
void read_childproc(int sig)
{
pid_t pid;
int status;
pid = waitpid(-1, &status, WNOHANG);
printf("remove proc id : %d \n", pid);
}
void errorhandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
上述代码涉及到的模型例如如下:
这里咱们讨论使用 I/O
复用解决每个客户端请求都建立进程的资源消耗弊端。
运用select
函数是最具备表明性的实现复用server端方法。使用该函数的时候可以将多个文件描写叙述符集中到一块儿进行监视。
咱们将监视项称为事件。发生了监视项对应的状况时,称“发生了事件”。
select
函数很是难使用,但是为了实现I/O
复用server端,咱们应该掌握该函数。并运用到套接字编程中。以为“select
函数时I/O
复用的所有内容”并不为过。
将要监视的套接字集合在一块儿,集中式也要依照监视项(接收、传输、异常)进行区分。使用fd_set
数组变量运行此项操做。
在数组中注冊或者更改值的操做应该由下列宏完毕:
FD_ZERO(fd_set * fdset): 所有位初始化为0
FD_SET(int fd, fd_set * fdset): 注冊文件描写叙述符fd 的信息
FD_CLR(int fd, fd_set * fdset): 清楚文件描写叙述符的信息
FD_ISSET(int fd, fd_set * fdset): 是否监视
上述定义可在如下直观看到效果:
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set * readset, fd_set * writeset, fd_set * excepyset, const struct timeval * timeout); // 成功时返回大于0的值。 失败返回-1
~ maxfd: 监视对象文件描写叙述符的数量
~ readset: 是否存在待读取数据
~ writeset: 是否可传输无堵塞数据
~ exceptset: 是否发生异常
~ timeout: 调用该函数以后,为防止陷入无限堵塞的状态,传递超时信息
返回值: 错误发生返回-1, 超时返回0,因发生关注的事件返回时。返回大于0的值,该值是发生事件的文件描写叙述符数
“文件描写叙述符的监视范围?”
函数要求经过第一个參数传递监视对象文件描写叙述符的数量。所以需要获得注冊在fdset
变量中的文件描写叙述符数。
但每次新建文件描写叙述符时。其值都会加1,故将最大的文件描写叙述符加1再传递到函数就能够。加1是因为文件描写叙述符的值从0開始。
“怎样设置超时时间?”
struct timeval
{
long tv_sec; // seconds
long tv_usec; // microseconds
}
函数仅仅有在监视的文件描写叙述符发生变化时才返回。假设未发生变化就会进入堵塞状态。指定超时时间就是为了防止这样的状况的发生。
即便文件描写叙述符未发生变化,仅仅要到了指定时间函数也会返回。固然,返回值是0。假设不设置超时,传递NULL
就能够。
假设函数返回值大于0,说明对应数量的文件描写叙述符发生变化。
文件描写叙述符变化是指监视的文件描写叙述符发生了对应的监视事件
那么,怎样得知哪些文件描写叙述符发生了变化呢?
向函数传递的第二个到第四个參数传递的fd_set
变量将发生例如如下图所看到的的变化:
函数调用以后,向其传递的fd_set
变量将发生变化,原来为1的所有位将变成0,但发生变化的位除外。换句话说就是。调用以后,发生变化的文件描写叙述符的位将为1。
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>
#define BUF_SIZE 30
int main(int argc, char *argv[])
{
fd_set reads, temps;
int result, str_len;
char buf[BUF_SIZE];
struct timeval timeout;
FD_ZERO(&reads);
FD_SET(0, &reads);
while(1)
{
temps = reads;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
result = select(1, &temps, 0, 0, &timeout);
if(result == -1)
{
puts("select() error");
break;
}
else if(result == 0)
{
puts("Time out");
}
else
{
if(FD_ISSET(0, &temps))
{
str_len = read(0, buf, BUF_SIZE);
buf[str_len] = 0;
printf("message from console : %s", buf);
}
}
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>
#define BUF_SIZE 100
void errorhandling(char *message);
int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
struct sockaddr_in serv_adr, clnt_adr;
struct timeval timeout;
fd_set reads, cpy_reads;
socklen_t adr_sz;
int str_len, fd_max, fd_num, i;
char buf[BUF_SIZE];
if(argc != 2)
{
printf("Usage : %s <port> \n", argv[0]);
exit(1);
}
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
errorhandling("bind() error");
puts("bind sucess");
if(listen(serv_sock, 5) == -1)
errorhandling("listen() error");
puts("listen success");
FD_ZERO(&reads);
FD_SET(serv_sock, &reads);
fd_max = serv_sock;
while(1)
{
cpy_reads = reads;
timeout.tv_sec = 5;
timeout.tv_usec = 5000;
if((fd_num = select(fd_max+1, &cpy_reads, 0, 0, &timeout)) == -1)
{
errorhandling("select() error");
break;
}
if(fd_num == 0)
{
puts("Time out");
continue;
}
for(i=0; i < fd_max + 1; i++)
{
if(FD_ISSET(i, &cpy_reads))
{
if( i == serv_sock) //continue request
{
adr_sz = sizeof(clnt_adr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
FD_SET(clnt_sock, &reads);
if(fd_max < clnt_sock)
fd_max = clnt_sock;
printf("connected client : %d \n", clnt_sock);
}
else // read message
{
str_len = read(i, buf, BUF_SIZE);
if(str_len == 0) // close request
{
FD_CLR(i, &reads);
close(i);
printf("closed client : %d \n", i);
}
else
{
write(i, buf, str_len); //echo
}
}
}
}
}
close(serv_sock);
return 0;
}
void errorhandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
#include <sys/socket.h>
ssize_t send(int sockfd, const void * buf, size_t nbytes, int flags); // 成功返回发送的字节数。 失败返回-1
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
带外数据概念其实是向接收端传送三个不一样的信息:
SIGURG
信号或select
调用。本通知在发送进程发送带外字节后由发送端TCP
立刻发送。即便往接收端的不论什么数据发送因流量控制而中止了。TCP
仍然发送本通知。
本通知可能致使接收端进入某种特殊处理模式,以处理接收的不论什么后继数据。
既然TCP
是一个不解释应用进程所发送数据的字节流协议。带外字节就可以是不论什么8位值。
对于TCP
的紧急模式。咱们可以以为URG
标志时通知(信息1),紧急指针是带外标记(信息2),数据字节是其自己(信息3)。
与这个带外数据概念相关的问题有:
TCP
紧急指针;带外数据的一个常见用途体现在rlogin
程序中。当客户中断运行在server主机上的程序时。server需要告知客户丢弃所有已在server排队的输出,因为已经排队等着从server发送到客户的输出最多有一个窗体的大小。
server向客户发送一个特殊字节。告知后者清刷所有这些输出(在客户看来是输入),这个特殊字节就做为带外数据发送。
客户收到由带外数据引起的SIGURG
信号后。就从套接字中读入直到碰到带外数据。
客户收到由带外数据引起的SIGURG
信号后,就从套接字中读入直到碰到带外标记,并丢弃到标记以前的所有数据。
这样的情形下即便server相继地高速发送多个带外字节,客户也不受影响。因为客户仅仅是读到最后一个标记为止,并丢弃所有读入的数据。
总之。带外数据是否实用取决于应用程序使用它的目的。假设目的是告知对端丢弃直到标记处得普通数据,那么丢失一个中间带外字节及其对应的标记不会有什么不良后果。但是假设不丢失带外字节自己很是重要,那么必须在线收到这些数据。另外。做为带外数据发送的数据字节应该差异于普通数据,因为当前新的标记到达时,中间的标记将被覆写,从而其实把带外字节混杂在普通数据之中。
举例来讲,telnet
在客户和server之间普通的数据流中发送telnet
本身的命令。手段是把值为255
的一个字节做为telnet
命令的前缀字节。(值为255的单个字节做为数据发送需要2个相继地值为255的字节。
)这么作使得telnet
可以区分其命令和普通用户数据,只是要求客户进程和server进程处理每个数据字节以寻找命令。
除紧急指针(URG
指针)指向的一个字节外,数据接收方将经过调用常用输入函数读取剩余部分。
设置MSG_PEEK
选项并调用recv
函数以后,即便读取了输入缓冲的数据也不会删除。所以,该选项一般与MSG_DONTWAIT
合做。用于调用非堵塞方式验证待读取数据存在与否。
对数据进行整合传输及发送的函数
经过writev
函数可以将分散保存在多个缓冲中的数据一并发送。适当使用这两个函数可以下降I/O
函数的调用次数。
#include <sys/uio.h>
ssize_t writev(int filedes, const struct iovec * iov, int iovcnt);
~ filedes: 数据传输对象的套接字文件描写叙述符
~ iov: iovec结构体数组的地址,结构体中包括待发送数据的位置和大小信息
~ iovcnt: 第二个參数的数组长度
struct iovec
{
void * iov_base; // 缓冲地址
size_t iov_len; // 缓冲大小
}
关系模型例如如下:
#include <stdio.h>
#include <sys/uio.h>
int main()
{
struct iovec vec[2];
char buf1[] = "ABCDEFG";
char buf2[] = "1234567";
int str_len;
vec[0].iov_base = buf1;
vec[0].iov_len = 3;
vec[1].iov_base = buf2;
vec[1].iov_len = 4;
str_len = writev(1, vec, 2);
puts("");
printf("Writen bytes : %d \n", str_len);
return 0;
}
#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovec * iov, int iovcnt);
readv.c