大家好,欢迎来到IT知识分享网。
第一部分 TCP/IP
TCP是一个复杂、可靠的字节流协议,而UDP是一个简单、不可靠的数据包协议。
理解connect、accept、close函数
TIME_WAIT状态
最长分节生命期:2MSL。
TIME_WAIT状态存在的两个理由:
1.可靠地实现TCP全双工连接的终止。
2.允许老的重复分节在网络中消逝。
端口号:
16位。范围0~65535。
0~1023 :众所周知端口。
1024~49151 :已登记的端口。
49152~65535:动态的或私用的端口。
常见端口号: HTTP 80 Telnet 23 FTP 20、21 SMTP 25 POP3 110 TFTP 69
ICMPx协议实现的网络诊断应用: ping 和 traceroute。
第二部分
第3章 套接字简介
1.IPV4套接字地址结构
struct in_addr{ in_addr_t s_addr; //32-bit }; struct sockaddr_in { uint8_t sin_len; sa_family_t sin_family; in_port_t sin_port; //16-bit struct in_addr sin_addr; //32-bit char sin_zero[8]; }; struct in_addr就是32位IP地址。
IPV4地址和TCP/UDP端口号在套接字地址结构中总是以网络字节序来存储(大端模式)。
2.主机字节序和网络字节序之间转换函数
主机转网络字节序: htons(); //16位 htonl(); //32位 网络转主机字节序 ntohs(); //16位 ntohl(); //32位
3.字节操纵函数
void bzero(void *dest ,size_t nbytes); //清零 void *memcpy(void *dest,const void *src,size_t nbytes);//内存拷贝 当源字节串与目标字节串重叠时 用memmove函数而不用那个memcpy函数。 inet_pton和inet_ntop函数 进行点分十进制数串和ip网络字节序二进制值间转换地址。IPV4和IPV6地址都适用。 #include<arpa/inet.h> int inet_pton(int family, const char*strptr, void *addrptr); //成功返回1,出错为-1 const char*inet_ntop(int family, const void *addrptr, char *strptr, size_len); //成功返回指向结果的指针,出粗偶返回NULL
第4章 基本TCP套接字编程
套接字编程之服务器模型
套接字之客户端编程模型
1.socket函数
int socket(int family, int type, int protocol); //成功返回监听描述符。用来设置监听,出错为-1 family是表示socket使用的协议类型,ipv4的话用AF_INET, 6的话AF_INET6。 type是创建的套接字类型,至少三种SOCK_STREAM(字节流套接字), SOCK_DGRAM(s数据包套接字), SOCK_RAW(原始套接字)。 protocol协议的标识,一般为0让系统选择,如果是原始套接字就要在这里进行设置以区分是链路层还是网络层或者传输层。
2.bind函数
用来将前面的监听描述符和服务器ip和端口绑定,设置服务器ip和端口。 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); //成功返回0 ,出错为-1 addr是套接字地址结构体的指针(地址),需要从sockaddr_in强制转换过来。 bind()函数可以指定IP地址或端口号,也可以两者都指定,也可以都不指定。
3.listen函数
int listen(int sockfd, int backlog); //成功返回0 ,出错为-1 listen函数把一个未连接的套接字转换成一个被动套接字,指示内核应接受指向该套接字的连接请求。 backlog是队列中已经完成三次握手和未完成三次握手的所有连接个数。
4.accept函数
前面的步骤只是完成三次握手,然后将一条条连接放入队列,这一步从队列中拿出一个进行通信。 int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); //成功返回非负描述符,错误返回-1 //accept(listenfd,NULL,NULL);//不关心客户的地址 cliaddr用来返回客户端的地址结构体。 addrlen是一个指针,暗示着用来返回某个值,就是客户端地址结构体的大小。 输入的时候需要给一个初值(用来装地址结构体的对象的大小),称为值--结果型参数。 成功返回值是一个可以用来read/write的已连接套接字描述符。
5.connect函数
int connect(int sockfd, const struct sockaddr *seraddr, socklen_t addrlen); //成功返回0 ,出错为-1 seraddr是包含了服务器的ip和端口的地址结构体。 连接后sockfd就是用来write和read的fd。
6.fork 和 exec函数
#include <unistd.h> pid_t fork(void); //返回值:在子进程中为0,父进程中为子进程ID,出错则为-1 fork()函数调用一次,返回两次。 fork两个典型用法: 1.一个进程创建一个自身的副本,这样每个副本都可以在另一个副本执行其他任务的同时处理各自的某个操作。 2.一个进程执行另外一个进程。调用fork产生子进程,再调用exec把自身替换成新程序。
7.getsockname 和 getpeername 函数
#include <sys/socket.h> int getsockname(int sockfd,struct sockaddr *localaddr,socklen_t *addrlen); //成功为0 ,出错为-1 //getsockname 用于获取某个套接字的地址族。用于返回由内核赋予该连接的本地IP地址。 int getpeername(int sockfd,struct sockaddr *peeraddr,socklen_t *addelen); //成功为0 ,出错为-1 //当一个服务器是由调用过accept的某个进程通过exec执行程序时,它能够获取客户身份的唯一途径就是调用getpeername
第5章 TCP客户端/服务器程序示例
1.POXI信号处理
信号的发送/产生 1)硬件(内存读取)错误时产生,如段错误信号。 2)在有终端的时候,键盘组合产生,如SIGINT和SIGQUIT 3)软件错误时产生,如除0错误信号 4)使用kill系统调用发送 5)使用raise给自己发送信号。 6)闹钟到期后发生,给自己发送 收到信号后的响应 1)默认行为(终止本进程) 2)当信号不存在,不理睬。例如,父进程在收到子进程发来的SIGCHLD信号。 3)忽略信号。经过signal函数的设置,将信号完全消灭掉。 4)安装处理函数,处理这个信号。 两个特殊的信号是无法忽略和处理的,SIGKILL(9) 和SIGSTOP(19)
wait 和 waitpid函数 pid_t wait(int *staloc); pid_t waitpid(pid_t pid,int *ststloc ,int options); /*返回:成功返回进程ID,出错为0或-1*/ pid:从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。 pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。 pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。 pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。 pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。 options: options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用 fork子进程时,必须捕获SIGCHLD信号 区别:当多个信号同时要处理时,用waitpid而不是wait
2.服务器的几种异常终止
1、在accept函数返回前连接夭折
这种情况发生在TCP 3次握手刚好完成,服务器TCP将连接放入到已经建立好连接队列中,此时客户端给一个RST,接下来accept返回,不过这时accept返回的是ECONNECTABORT错误.这不是一个致命错误。
2、服务器进程终止
过程如下:
a、kill掉服务子进程的进程ID,作为进程善后处理的部分,所有打开的文件描述符被关闭,这导致服务端TCP发送FIN给客户端,客户端TCP响应以ACK。
b、客户端此时正阻塞在fgets函数调用上,这导致客户端不知道服务端TCP已经关闭连接。
c、客户端在fgets返回后调用write向服务端发数据,由于服务端已经被kill掉,所以服务端TCP会发送一个RST给客户端TCP.
d、客户端在发送完数据后立即调用read读取数据,由于有第一步的FIN,read立即返回0(表示EOF),然而客户端希望的是收到刚才发送的数据而不是EOF。如果客户端接着往服务端发数据,将诱发服务端TCP向服务端发送SIGPIPE信号,因为向接收到RST的套接口写数据都会收到此信号.
问题的本质在于客户端同时处理两个描述字–套接口和用户输入,程序被单纯地阻塞在一个源上了。这个问题可以通过1、设置非阻塞模式。2、采用select以及epoll处理。
3、服务器主机崩溃
在客户TCP发送数据后,由于接收不到ACK,它将试图一直重传,直到最后放弃,并返回给客户进程一个出错信息。ETIMEOUT表示没有相应,EHOSTUNREACH表示路由器判定主机不可达。
4、服务器崩溃后重启
由于服务端TCP丢失了以前的连接信息,这将导致服务端发送一个RST,而此时客户端阻塞在read函数,这将导致返回一个ECONNECTRESET错误.
5、服务器关机
服务器关机时init进程会先发送SIGTERM(此信号可捕获)给所有进程,再过一段时间发送SIGKILL(次信号不可捕获)给仍然在运行的程序,这时就和服务器进程终止一样了。必须在客户中使用select或poll函数,使得服务器的终止一经发生,客户就能检测到。
第6章 I/O复用:select 和 epoll 函数
1.Unix下5种I/O模型
- 阻塞型I/O
默认情况下,所有套接字都是阻塞的。 - 非阻塞型I/O
使用read的时候,没有数据可读,则立即返回, 错误码是EWOULDBLOCK。这种情况需要多次去read - I/O复用(select 和 epoll)
通过某个观察者来观察究竟哪个fd已经准备好,将相应的fd通知给应用程序。这种机制可以等待多个fd准备好。 - 信号驱动I/O(SIGIO)
一旦资源可用就发出一个信号,然后用信号处理函数来处理它。
这个信号就是SIGIO信号。 - 异步I/O
2.I/O复用(select 和 epoll)
1、select机制
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 注意:nfds参数为待测试的最大描述符加1,timeout参数为NULL永远等待,通过结构体struct timeval可以设置等待的时间的秒数和微秒数。 void FD_CLR(int fd, fd_set *set); //将某个描述符清除 int FD_ISSET(int fd, fd_set *set); //判断是否已经置位,也就是是否保存 void FD_SET(int fd, fd_set *set); //将描述符放入集合 void FD_ZERO(fd_set *set); //清除所有的位,描述符的初始化很重要。
SELECT 客户端
#include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <fcntl.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { int sockfd=socket(AF_INET,SOCK_STREAM,0); struct sockaddr_in sin_server; bzero(&sin_server,sizeof(sin_server)); sin_server.sin_family=AF_INET; sin_server.sin_port=htons(2018); inet_pton(AF_INET,"127.0.0.1", &sin_server.sin_addr); int ret=connect(sockfd,(struct sockaddr *)&sin_server,sizeof(sin_server)); if(ret==-1) { perror("connect"); } while(1) { fd_set fdread; FD_ZERO(&fdread); FD_SET(0,&fdread); FD_SET(sockfd,&fdread); select(sockfd+1, &fdread,NULL,NULL, NULL); //判断是否0号描述符准备好 if(FD_ISSET(0,&fdread)) { char buffer[1024]={
0}; read(0,buffer,sizeof(buffer)); write(sockfd,buffer,strlen(buffer)); printf("字符串已发送\n"); } //判断是否sockfd准备好 if(FD_ISSET(sockfd,&fdread)) { char buffer[1024]={
0}; ret=read(sockfd,buffer,sizeof(buffer)); printf("读到的数据是%s\n",buffer); close(sockfd); } } }
2.epoll函数
常用函数: int epoll_create(int size); //创建一个epoll对象,返回的是它的描述符 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //用来将文件描述符加入epoll对象 epfd是前面创建的epoll对象 op有如下选项: EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CT_MOD struct epoll_event的结构体如下: struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; events有如下选项: EPOLLIN: 表示该描述符是用来读的, EPOLLOUT: 相反 EPOLLLT: 水平触发, 有数据后会一直提醒 EPOLLET: 边缘触发, 有数据的时候只会提醒一次,就好比上升沿只有一次一样 data是一个联合体,通常只使用里面的fd这个值 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); //等待,相当于select events用来返回可用epoll_event的结构体, maxevents是结构体的数组的大小。 timeout是超时时间,单位是ms,如果为-1,表示永远等待,0不等待。 第一步:epoll_create()系统调用。此调用返回一个句柄,之后所有的使用都依靠这个句柄来标识。 第二步:epoll_ctl()系统调用。通过此调用向epoll对象中添加、删除、修改感兴趣的事件,返回0标识成功,返回-1表示失败。 第三部:epoll_wait()系统调用。通过此调用收集收集在epoll监控中已经发生的事件。
epoll客户端
#include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <fcntl.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/epoll.h> int main(void) { int sockfd=socket(AF_INET,SOCK_STREAM,0); struct sockaddr_in sin_server; bzero(&sin_server,sizeof(sin_server)); sin_server.sin_family=AF_INET; sin_server.sin_port=htons(2018); inet_pton(AF_INET,"127.0.0.1", &sin_server.sin_addr); int ret=connect(sockfd,(struct sockaddr *)&sin_server,sizeof(sin_server)); if(ret==-1) { perror("connect"); } struct epoll_event ev, resutl[10]; int epfd=epoll_create(256); ev.data.fd=0; ev.events=EPOLLIN;//用来监测输入 epoll_ctl(epfd,EPOLL_CTL_ADD,0,&ev); ev.data.fd=sockfd; ev.events=EPOLLIN; epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev); while(1) { //n表示有多少个事件准备好了,如果0则是超时返回 int n=epoll_wait(epfd,resutl,10,-1); int i; for(i=0;i<n;i++) { //判断是否0号描述符准备好 if(resutl[i].data.fd==0) { char buffer[1024]={
0}; read(0,buffer,sizeof(buffer)); write(sockfd,buffer,strlen(buffer)); printf("字符串已发送\n"); } //判断是否sockfd准备好 if(resutl[i].data.fd==sockfd) { char buffer[1024]={
0}; ret=read(sockfd,buffer,sizeof(buffer)); printf("读到的数据是%s\n",buffer); close(sockfd); } } } }
3.shutdown函数
shutdown函数可以不管引用计数就激发TCP的正常连接终止序列。
int shutdown(int sockfd,int howto); //成功为0 ,出错为-1 howto参数 SHUT_RD 关闭连接的读这一半 SHUT_WR 关闭连接的写这一半 SHUT_RDER 连接的读半部和写半部都关闭
第7章 套接字选项
1.getsockopt 和 setsockopt 函数
#include <sys/socket.h> int getsockopt(int sockfd,int level,int optname,void *optval socklen_t *optlen);/*获取*/ int setsockopt(int sockfd,int level,int optname,const void *optval, socklen_t optlen);/*设置*/ /*均返回:成功为0 ,出错为-1*/ sockfd指向打开的描述符,level指定系统中解释选项的代码或通用套接字代码, optval是一个指向某个变量(*optval)的指针,*optval 0表示禁用相应选项,非0 表示相应选项被启用。 level:SOL_SOCKET 、IPPROTO_IP等。 optname:SOL_SOCKET级别中有 SO_LINGER、SO_REUSEADDR、SO_RCVBUF等。
2.SO_LINGER选项
本选项指定close函数对面向连接的协议如何操作。默认close立即返回,如果有数据残留在套接字发送缓冲区中,系统将试着把这些数据发送给对端。
struct linger{ int l_onoff; int l_linger; }; 开启选项的情况下: l_onoff l_linger false X l_linger值无关 true 0 不延迟, 产生rst信号 true >0 延迟多少秒再关闭
close立即返回,不等待
close拖延到了对于客户端FIN的ACK才返回
3.SO_RCVBUF 和 SO_SNDBUF选项
改变发送缓冲区和接收缓冲区的大小。
4.SO_REUSEADDR 和 SO_REUSEPORT 选项
地址复用:在tcp关闭的时候,会经历一个time_wait状态,在此期间,同一个ip地址+端口是不能再次使用的。
SO_REUSEADDR 允许单个进程捆绑同一端口到多个套接字上(只要每次捆绑指定不同的本地ip地址即可),允许完全重复的捆绑。
5.TCP_NODELAY 选项
本选项禁用TCP的Nagle算法。
不合适使用Nagle算法和TCP的ACK延滞算法的客户是以若干小片数据项服务器发送单个逻辑请求的客户。
可以使用writev,单个writev调用最终导致调用TCP输出功能一次而不是两次。
第8章 基本UDP套接字编程
1.recvfrom 和 sendto 函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); /*不关心数据发送者地址,后两个参数置NULL*/ /*数据包丢失客户端会永远阻塞与recvfrom调用的解决办法,给客户的recvfrom调用设置一个超时*/ ssize_t sendto(int sockfd, void *buf, size_t len, int flags, struct sockaddr *dst_addr, socklen_t *addrlen); /*均返回:成功为读或写的字节数,出错为-1*/
2.UDP 服务端和客户端模型
3.UDP 的connect函数
对于已连接UDP套接字调用connect的结果
1.不能再给输出操作指定目的的IP地址和端口号。也就是不使用sendto,而用write或send。 2.不必使用recvfrom以获取数据包的发送,改用read、recv或recvmsg。 3.由已连接UDP套接字引发的异步错误会返回给它们所在的进程,而未连接UDP套接字不接收任何异步错误。
第11章 名字与地址转换
1.DNS
域名系统(Domain Name System,DNS):用于主机名字与IP地址之间的转换。 主机名:可以是简单名字或者全限定域名。 客户端和服务器等应用程序通过调用解析器(函数库中的函数)接触DNS服务器。
2.gethostbyname 和 gethostbyaddr 函数
#incude <netdb.h> struct hosten *gethostbyname(const char *hostname); 返回:成功返回非空指针(只能返回IPV4地址),出错为NULL且设置h_errno, 用hstrerror函数解释这个h_errno 返回的非空指针指向如下的hostent结构 struct hostent{ char *h_name; //主机规范名字 char h_aliases; //别名 int h_addrtype; // AF_INET :host address type int h_length; //地址长度 char h_addr_list; //地址列表 }; struct hosten *gethostbyaddr(const char *addr,socklen_t len,int family); addr 不是char*类型,而是一个指向存放IPV4地址的某个in_addr结构的指针。
2.getservbyname 和 getservbyport 函数
处理服务名和端口号的常用函数是getservvyname,接受一个服务器名作为参数,并返回包含相应端口号的结构。
#inclulde <netdb.h> struct servent *getsservbyname(const char *servname,const char *protoname); 例: struct servent *sptr; sptr = getservbyname("ftp","tcp"); 返回:成功为非空指针,出错为NULL servent 结构体如下 struct servent{ char *s_name; //规范名字 char s_aliases; //别名列表 int s_port; //端口号 ,网络字节序 char *s_proto; //使用的协议 }; struct servent *getservbyport(int port,const char *protoname); 例: struct servent *sptr; sptr= getservbyport(htons(53),"tcp");
第13章 守护进程和inetd超级服务器
守护进程(daemon):在后台进行并独立于所有终端控制的进程。
1.syslog 函数
#include <syslog.h> void syslog(int priority,const char *message,...); //具体参数的意义可以man看一下。 用于从守护进程中登记消息。因为守护进程没有控制终端,所以不能把消息fprintf到stderr上。 void openlog(const char*ident,int options,int facility); // ident 程序名 ,options具体选项man看一下,facility默认0为LOG_USER void closelog(void); openlog可以在首次调用syslog前调用,closelog在应用进程不需要再发送日志消息时调用。
2.守护进程的创建
(1)调用fork让程序转入后台运行。 (2)脱离终端。 用setsid设置会话组长。 (3)再次fork以避免无意中获得新的控制终端 (4)改变工作目录和文件创建模式掩码 chdir("/") 和 umask(0) (5)关闭所有非必要的文件描述符。将0,1,2描述符转到null文件中。 for(i=0;i<1024;i++) { close(i); } open("/dev/null",O_RDONLY);//0 open("/dev/null",O_RDWR);//1 open("/dev/null",O_RDWR);//2 openlog(pname,LOG_PID,facility); (6)屏蔽SIGHUP信号 signal(SIGHUP,SIG_IGN); 代码实现: daemon_init(const char *pname,int facility) { int i; pid_t pid; if((pid = fork()) < 0) return -1; else if(pid) exit(0); if(setsid()<0) return -1; signal(SIGHUP,SIG_IGN); if((pid = fork()) < 0) return -1; else if(pid) exit(0); chdir("/"); for(i=0;i<1024;i++) { close(i); } open("/dev/null",O_RDONLY);//0 open("/dev/null",O_RDWR);//1 open("/dev/null",O_RDWR);//2 openlog(pname,LOG_PID,facility); return 0; }
3.inetd 守护进程
第 14 章 高级 I/O 函数
1.套接字的I/O上设置超时的3种方法
(1)调用alram,在指定超时期长生SIGALRM信号。 可用SIGALRM 为 connect 、recvfrom设置超时。 (2)在select中阻塞等待I/O,以代替直接在read或write上调用。 (3)使用新的SO_RCVTIMEO 和 SO_SNDTIMEO 套接字选项。但问题在于并非所有实现都支持这两个套接字选项。 两者都不能用于为connect设置超时。
2.recvmsg 和 sendmsg 函数
最通用的I/O函数。
#include <sys/socket.h> ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags); 返回:读入或写出字节数——成功;-1——出错
msghdr结构:
struct msghdr { void *msg_name; /* protocol address */ socklen_t msg_namelen; /* size of protocol address */ struct iovec *msg_iov; /* scatter/gather array */ int msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data (cmsghdr struct) */ socklen_t msg_controllen; /* length of ancillary data */ int msg_flags; /* flags returned by recvmsg() */ }; msg_name和msg_namelen用于套接字未连接的场合,不指明协议地址,masg_name置NULL msg_iov 和msg_iovlen指定输入或输出缓冲区数组。 msg_control 和 msg_controllen指定可选的辅助数据的位置和大小。 只有recvmsg使用msg_flags成员。(查表)
3.标准I/O函数库的三类缓冲
(1)完全缓冲。 标准输入和标准输出,除非他们指代中断设备。 缓冲区满,进程显示调用fflush或进程调用exit终止自身。 (2)行缓冲。 碰到换行符,进程调用fflush或进程调用exit终止自身。 (3)不缓冲。 每次调用标准I/O输出函数都发生I/O。错误输出。
第15章 Unix域协议
Unix域套接字:
用于在同一主机上的不同进程之间传递描述符,Unix域套接字往往比通信两端位于同一个主机的TCP套接字快出一倍。
地址结构: #include <sys/un.h> struct sockaddr_un{ sa_family_t sun_family; /*AF_LOCAL*/ char sun_path[1024]; /*null-terminated pathname */ } sun_path数组中的绝对路径名必须以空字符结尾。如果系统中已存在该路径名,调用ulink删除这个路径名。
第16章 非阻塞式I/O
使用read的时候,没有数据可读,则立即返回, 错误码是EWOULDBLOCK。这种情况需要多次去read。对于write也是一样的。 设置方法: 使用fcntl来设置,将文件的选项设置为O_NONBLOCK。 int fcntl(int fd, int cmd, ... /* arg */ ); 读取文件打开标志:F_GETFL 设置文件打开标志:F_SETFL
/* 设置非阻塞,参数是需要设置的fd 返回值如果>=0则表示成功 失败返回-1,错误保存保存在错误码中 */ int set_nonblock(int fd) { //设置非阻塞 //拿出原有的标识位 int flag=fcntl(fd,F_GETFL); flag|=O_NONBLOCK;//加上非阻塞标识位 //再写回去 int ret=fcntl(fd,F_SETFL,flag); if(ret==-1) { //设置一下错误码 errno=EIO; return -1; } return fd; }
第17章 ioctl操作
ioctl函数 #include <unistd.h> int ioctl(int fd,int request,...../* void *arg /); //rquest 参数查表 //返回:若成功则为0.失败则为-1 其中第三个参数总是一个指针,但指针的类型依赖于request参数。我们可以把和网络相关的请求划分为6类: 1)套接字操作 要求ioctl的第三个参数为指向某个整数的一个指针。 2)文件操作 要求ioctl的第三个参数指向一个整数。 3)接口操作 4)ARP高速缓存操作 5)路由表操作 6)流系统
第24章 带外数据
1.TCP带外数据
TCP并没有真正的带外数据,而是提供了紧急模式。
然后以MSG_OOB标志调用send函数写字符a的单字节带外数据
send(fd,"a",1,MSG_OBB);
TCP首部设置URG标志,并把紧急偏移字段设置为指向带外字节之后的字节。
OBB是否发送取决于在套接字发送缓冲区中先于它的字节数、TCP准备发送给对端的分节大小以及对端通告的当前窗口。
如果发送多个字节的带外数据
send(fd,"abc",3,MSG_OBB);
那最后的那个字节(字母c)被认为是带外字节。
2.接收端
(1)当收到一个设置了URG标志的分节时,接收端TCP检查紧急指针,确定它是否指向新的带外数据。只有第一个到达的会导致通知接收进程有新的分节到达。 (2)当有新的紧急指针达到时,接收进程被通知到。(前提是接收进程调用fcntl或ioctl为这个套接字建立了属主)。只有一个OOB标志,如果新的OOB字节在旧的OOB字节被读取之前就到达,旧的OOB字节会被丢弃。 (3)由紧急指针指向的实际数据字节到达接收端TCP时,该数据既可能被拉出带外,也可能被留在带内,即在线留存。SO_OOBINLINE套接字选项默认情况下是禁止的,接收端套接字把该数据字节放入到该连接的一个独立的单字节带外缓冲区。(唯一接收方法是指定MSG_OOB标志调用recv、recvfrom或recvmsg)
3.开启SO_OOBINLINE套接字选项会发生的一些错误
(1)接收进程请求读入带外数据(通过MSG_OOB标志),但对端尚未发送任何带外数据,读入操作将返回EINVAL。 (2)在接收进程已被告知对端发送了一个带外字节的前提下,如果接收进程试图读入该字节,但是该字节尚未到达,读入操作将返回EWOULDBLOCK。 (3)接收进程试图多次读入同一个带外字节,读入操作将返回EINVAL。 (4)接收进程已经开启了SO_OOBINLINE套接字选项,后来试图通过指定MSG_OOB标志读入带外数据,读入操作将返回EINVAL。
4.sockatmark函数
用sockatmark函数确定是否处于带外标记。
#include <sys/socket.h> int sockatmark(int sockfd); /*返回:处于带外标记为1,不处于带外标记为0,出错为-1*/
第26章 线程
1.线程
父子线程的资源共享问题
共享:
全局变量,打开的文件(描述符),文件系统的参数,环境变量。
一、线程 进程里面执行流程的一条。每一个进程至少(默认)有一个线程,被称为主线程。可以在主线程的基础上增加新的线程,为了同时执行多个任务。这种情况叫做多线程。 进程是资源分配的最小单位,线程是执行的最小单位。一个进程里有多个线程。 在linux里面,线程和进程的内部实现是非常接近的,linux里线程是一个轻量级的进程。都是用struct task_struct这个结构体来表示的。 二、线程的编号 如同pid一样,线程也有一个编号,使用pthread_t类型来表示。本质上是一个整型数。 可以使用pthread_self(), 相当于进程的getpid函数。 编译的时候要加上 -pthread这个库。 三、线程的创建 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 第一个参数thread,存储了新线程的id。是一个输出型的参数。 attr是属性,可以在创建的时候提供。可以为NULL。 void *(*start_routine) (void *)是一个指向函数的指针。 线程所需要执行的任务,就是这个函数。 最后一个是传给那个函数的参数。可以为NULL。 线程创建的过程: 使用的都是clone系统调用,只是参数和进程创建不同而已。 四、线程属性的获取和设置 每一个线程都有属性的。相关的设置和获取函数都是以pthread_attr_开头的。 属性有: 使用的栈的地址,大小。 调度方式: 和其它进程(线程)竞争的方式。(有两种,一是和所有的其它进程一起竞争,二是和本进程里的线程竞争)。 属性的数据类型是pthread_attr_t。 五、线程的退出 (1)子线程的执行范围只限制在交给它的函数里。在函数的return的地方退出。 (2)子线程也可以使用类似进程的exit函数,形式是pthread_exit()。 void pthread_exit(void *retval); (3)其它线程使用pthread_cancel函数结束某个线程。 int pthread_cancel(pthread_t thread); thread是取消的对象。 六、等待线程 和进程类似,也有等待函数。 int pthread_join(pthread_t thread, void retval); thread是等待的对象,retval是对象的函数返回值。 这个函数会阻塞自己等待指定的子线程结束,然后处理它的垃圾。 返回值有如下几种情况: (1)函数正常返回,则保存的是函数的return 值。 (2)如果使用pthread_exit,则保存exit里的参数。 (3)如果别的线程通过pthread_cancel来结束,则保存的是PTHREAD_CANCELED这个宏。 七、线程的分离 主线程和子线程分离,主线程不再等待子线程,子线程独立运行。 默认创建的子线程是不分离的。如果要分离,有两种途径: (1)使用pthread_detach (2)使用pthread_attr_setdetached修改线程属性。 int pthread_detach(pthread_t thread); thread是分离的对象。
2.线程同步
和进程同步类似,线程同步更多的是保护某个资源对象,让任意时刻只有一个线程访问它。而进程同步是保护某个代码临界区。
互斥锁和条件变量。
1.互斥锁 这种锁只有0和1两种状态。 相关的操作: pthread_mutex_t :数据类型 pthread_mutex_init :初始化,也可以使用宏的方式初始 pthread_mutex_destroy: 销毁 pthread_mutex_lock: 上锁 pthread_mutex_unlock: 解锁 2.条件变量 相关的操作函数: pthread_cond_t : 数据类型 pthread_cond_wait: 睡眠等待 pthread_cond_signal: 唤醒等待的线程(单个) pthread_cond_broadcast: 通知所有等待的线程(多个) 条件变量结合互斥锁可以让主循环进入睡眠,知道某个线程通知它有事可做才醒来。
存钱取钱例子:
#include <unistd.h> #include <pthread.h> #include <stdio.h> #include <signal.h> //账户 unsigned int account=0; pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond=PTHREAD_COND_INITIALIZER; int c=0; //用来触发的 int tiaojian=0; //条件 void handler(int sig) { if(sig==SIGINT) { printf("信号发生\n"); c=1; } } //线程函数 void *thread_fun(void *arg) { while(1) { sleep(1); //存钱 if(c>0) { pthread_mutex_lock(&mutex); tiaojian=1;//先设置条件为真 pthread_mutex_unlock(&mutex); printf("存钱\n"); pthread_cond_signal(&cond); c=0; } } return NULL; } int main(void) { signal(SIGINT,handler); pthread_t thread; pthread_create(&thread,NULL,thread_fun,NULL); while(1) { pthread_mutex_lock(&mutex); while(tiaojian==0) pthread_cond_wait(&cond,&mutex); tiaojian=0; printf("醒来取钱\n"); pthread_mutex_unlock(&mutex); } pthread_join(thread,NULL); }
“`
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/143431.html