套接字编程简介
项目:UNIX网络编程学习
作者:曾金龙
供职:(深圳迅雷网络技术股份有限公司)
领域:迅雷下载库研发
日期:2014-07-25
1, TCP连接图
socket编程,过眼烟云的去看,无外乎就那么几个API,但是,如果想登堂入室,必须注重里面的每一个细节。
对于TCP编程而言,最重要的是记住这么一幅图。死记的基础上理解。
windows下有visio,ubuntu下只好用Dia,不是很习惯,而且不支持中文输入。
图1 TCP连接的数据包交换图
你特别需要注意的是:
1)哪个地方会被阻塞住,例如读取数据的时候,如果对方没有写数据,则会被阻塞,对方要是关闭了连接则会读取到0(EOF);
2)在每一个阶段如果顺序发生了变化,将会是怎么样。例如,我们总是习惯对方写,我们读,然后我们写,对方读,如果不读怎么样?或者一直写。write是很少会被阻塞的,除非写满了了内核的缓冲区,free空间比它的下限还要小。当然一旦free空间大于其下限,Epoll等待的时候是会被唤醒的,EPOLLOUT,表示可以写。
3)如果彼此在不同的时机宕机了,脱离网络了,拒绝访问了,关闭了等,又将会发
生什么?会有一些是TIME_OUT,一些是会受到RST,一些是受到FIN。这个就是TCP的细节。而细节决定成败。
在迅雷的网络模组中,已经把socket打包在公共库里面,配合异步框架使用。前辈们的代码还是很优秀的。特别是用select ,poll,epoll,配合signal,pipe,mutex等这些实现的异步框架,是迅雷架构里面最优美的代码之一。(迅雷用C实现C++的代码也值得细品)
2 地址结构
编程要用到的地址结构就是这个,再在用socket函数的时候强制类型转换成struct sockaddr
struct in_addr{in_addr_t s_addr;};
struct sockaddr_in{ uint8_t sin_len; sa_family_t sin_family; in_port_t sin_addr; char sin_zero[8]; };
struct sockaddr{ uint8_t sa_len; sa_family_t sa_family; char sa_data[14]; };
其他的一般还用不到,用到再查阅。
对了,有些博客你会发现它们写的结构不太一样,就是没有开头的sin_len和sa_len,这个问题在书中有说过,为了兼容了ISO,后来在BSD4.3的时候加入的,其实,作为一个数据结构,在头部加一个本结构的长度,就实现了该结构可变长。
3 主机--网络字节顺序问题
不同的主机它存在大小端问题,所以在网络中交换的数据,必须处理大小端不一致的问题。
3.1)如何检测自己的机子是大端还是小端
书中定义了一个union联合体,其实,不用。
BOOL isBigEndian() { short v=0x0102; char* c=(char*)&v; if(c[0]==0x01&&c[1]==0x02)return TRUE; else return FALSE; }
3.2)主机字节顺序和网络顺序之间的转换函数
#include <netinet/in.h> uinit16_t htons(uint16_t host16bitvalue); uinit32_t htonl(uint32_t host32bitvalue); uinit16_t ntohs(uint16_t nett16bitvalue); uinit32_t ntohl(uint32_t net32bitvalue);
3.2)内存操作函数
#include <strings.h> void bzero(void* dest,size_t nbytes); void bcopy(const void* src ,void* dest,size_t nbytes); int bcmp(const void *ptr1,const void *ptr2,size_t nbytes);
#include <string.h> void* memset(void* dst, int c, size_t len); void* memcpy(void* dst,const void* src,size_t nbytes); int memcpy(const void* ptr1,const void* ptr2,size_t nbytes);
下面我们通过测试代码一看究竟,bcopy和memcpy的区别。
/*author:ZengJinlong ,xunlei*/ #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <string.h> int main() { int a=0x01020304; printf("int size:%d\n",sizeof(int)); char* p_a=&a; printf("a adress:%p,p_a:%x\n",&a,p_a); if(p_a==&a) { printf("equal\n"); } printf("%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3]); if(p_a[0]==0x01&&p_a[1]==0x02)printf("Big-Endian\n"); else printf("Little-Endian\n"); printf("case 1\n"); char* p1=p_a+2; printf("before bcopy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]); printf("bcopy 0 to 2 ,2 size\n"); bcopy(p_a,p1,2); printf("0x%x\n",(int)*p1); printf("after bcopy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]); printf("case 2\n"); a=0x01020304; p1=p_a+1; printf("before bcopy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]); printf("bcopy 0 to 1, 2 size\n"); bcopy(p_a,p1,2); printf("0x%x\n",(int)*p1); printf("after bcopy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]); printf("case 3\n"); a=0x01020304; printf("before memcpy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]); printf("memcpy 0 to 2, 2 size\n"); char* p2=p_a+2; memcpy(p2,p_a,2); printf("after memcpy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]); printf("0x%x\n",(int)*p2); printf("case 4\n"); a=0x01020304; printf("before memcpy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]); printf("0x%x\n",a); p2=p_a+1; printf("memcpy 0 to 1 ,2 size\n"); memcpy(p2,p_a,2); printf("after memcpy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]); printf("0x%x\n",(int)*p2); return 0; }
void bcopy_new(const void* src,void* dest,size_t nbytes) { if(dest>src && ((unsigned)src+ nbytes > (unsigned)dest)) { size_t gap=(unsigned int)dest - (unsigned int)src; size_t overlap = nbytes-gap; int i=0; while(i<overlap) { *((char*)dest+gap+i)=*((char*)dest+i); ++i; } i=0; while(gap--)*((char*)dest++)= *((char*)src++); return; } while(nbytes--)*((char*)dest++)= *((char*)src++); return; }
将bcopy_new替换掉上面bocpy的例子,可以得出和bcopy一样的结果。