TCP常见的网络通信问题

本次 Live 主要包括以下内容 • TCP/IP协议栈层次与三次握手、四次挥手需要知道的细节 • TCP与UDP适用场景 • linux网络模型 • epoll_event结构中epoll_data_t的fd与ptr使用场景 •Windows网络模型 •异步connect •select可以检测网络异常吗 •epoll的水平模式和边缘模式 •阻塞与非阻塞socket的设置与区别 •send/recv返回值问题 •如何编写正确的收与发数据代码 •收发缓冲区如何设计 •SO_SNDTIMEO、SO_RCVTIMEO、TCP_NODELAY、SO_REUSEADDR和SO_REUSEPORT、SO_LINGER选项 •shutdown与优雅关闭 •错误码EINTR •tcp粘包问题 •信号SIGPIPE与EPIPE错误码 •gethostbyname阻塞与错误码 •SO_KEEPALIVE选项与心跳包设计技巧 •如何设计断线重连机制 •如何清除无效的死链 •网络框架中定时器不同实现 •http协议格式、head、get与post方法细节 •http、socks4与socks5代理编码实现 •你问我答互动环节 •总结 

 

技术面试中常见的网络通信细节问题解答

1. TCP/IP协议栈层次结构

2. TCP三次握手需要知道的细节点

3. TCP四次挥手需要知道的细节点(CLOSE_WAIT、TIME_WAIT、MSL)

4. TCP与UDP的区别与适用场景

5. linux常见网络模型详解(select、poll与epoll)

6. epoll_event结构中的epoll_data_t的fd与ptr的使用场景

7. Windows常见的网络模型详解(select、WSAEventSelect、WSAAsyncSelect)

8. Windows上的完成端口模型(IOCP)

9. 异步的connect函数如何编写

10.select函数可以检测网络异常吗?

11.你问我答环节一

12. epoll的水平模式和边缘模式

13. 如何将socket设置成非阻塞的(创建时设置与创建完成后设置),非阻塞socket与阻塞的socket在收发数据上的区别

14. send/recv(read/write)返回值大于0、等于0、小于0的区别

15.如何编写正确的收数据代码与发数据代码

16.发送数据缓冲区与接收数据缓冲区如何设计

17.socket选项SO_SNDTIMEO和SO_RCVTIMEO

18.socket选项TCP_NODELAY

19.socket选项SO_REUSEADDR和SO_REUSEPORT(Windows平台与linux平台的区别)

20.socket选项SO_LINGER

21.shutdown与优雅关闭

22.你问我答环节二

23.socket选项SO_KEEPALIVE

24.关于错误码EINTR

25.如何解决tcp粘包问题

26.信号SIGPIPE与EPIPE错误码

27.gethostbyname阻塞与错误码获取问题

28.心跳包的设计技巧(保活心跳包与业务心跳包)

29.断线重连机制如何设计

30.如何检测对端已经关闭

31.如何清除无效的死链(端与端之间的线路故障)

32.定时器的不同实现及优缺点

33.你问我答环节三

34.http协议的具体格式

35.http head、get与post方法的细节

36.http代理、socks4代理与socks5代理如何编码实现

37.ping

38.telnet

39.你问我答环节四

40.总结

 

--------------------------------------------------------------------------------------

 

技术面试中常见的网络通信细节问题解答

1. TCP/IP协议栈层次结构

 

2. TCP三次握手需要知道的细节点

答:1、三次握手,如果前两次有某一次失败,会重新从第一次开始,重来三次。

2、三次握手,如果最后一次失败,服务器并不会重传ack报文,而是直接发送RTS报文段,进入CLOSED状态。这样做的目的是为了防止SYN洪泛攻击。详情参见http://blog.csdn.net/libaineu2004/article/details/79020031

3、发起连接时如果发生TCP SYN丢包,那么系统默认的重试间隔是3s,这期间不会返回错误码。

4、如何模拟tcp挥手失败?答案是iptables命令可以过滤数据包,丢弃所有的连接请求,致使客户端无法得到任何ack报文。

TCP 的那些事儿(上)
TCP 的那些事儿(下)

 

3. TCP四次挥手需要知道的细节点(CLOSE_WAIT、TIME_WAIT、MSL)

答:http://blog.csdn.net/libaineu2004/article/details/78886213 再谈应用环境下的TIME_WAIT和CLOSE_WAIT

http://blog.csdn.net/libaineu2004/article/details/78886182 CLOSE_WAIT状态的原因与解决方法

http://blog.csdn.net/libaineu2004/article/details/78803068 TCP面试常见题:time_wait状态产生的原因,危害,如何避免

 

4. TCP与UDP的区别与适用场景

答:TCP协议栈本身是可靠,不会丢包,不会乱序,失败会重发。UDP需要应用层做协议来保证可靠性。视频可以用UDP。

必须使用udp的场景:广播。

 

5. linux常见网络模型详解(select、poll与epoll)

答:select和poll本质上没有区别,都是轮询,但是poll没有最大设备描述符数量的限制。

 

6. epoll_event结构中的epoll_data_t的fd与ptr的使用场景

答:在epoll模型中使用了一个struct epoll_event的结构体: 

typedef union epoll_data 

{ void *ptr;

 int fd; 

 uint32_t u32;

 uint64_t u64;

 } epoll_data_t; 

struct epoll_event 

{ uint32_t events; /* Epoll events */ 

 epoll_data_t data; /* User data variable */

 };

 

7. Windows常见的网络模型详解(select、WSAEventSelect、WSAAsyncSelect)

8. Windows上的完成端口模型(IOCP)

9. 异步的connect函数如何编写

参考博客:http://blog.csdn.net/analogous_love/article/details/60761528

 
  1. //关于tcp连接的异步connect实现流程如下:

  2. //(1)设置socket连接为非阻塞.

  3. //(2)调用connect函数.返回0表明连接成功.如果返回-1,同时errno为EINPROGRESS表明正在建立连接.

  4. //(3)使用select , epoll等 , 当描述符可写的时候检查连接状态.

  5.  
  6. #include <stdio.h>

  7. #include <fcntl.h>

  8. #include <sys/types.h>

  9. #include <sys/socket.h>

  10. #include <errno.h>

  11. #include <sys/epoll.h>

  12. #include <netinet/in.h>

  13. #include <string.h>

  14.  
  15. void setnonblock(int fd)

  16. {

  17. int flags = fcntl(fd, F_GETFL);

  18. fcntl(fd, F_SETFL, flags | O_NONBLOCK);

  19. }

  20.  
  21. int main(){

  22. const char* ip = "127.0.0.1";

  23. short port = 9999;

  24.  
  25. //设置连接地址

  26. struct sockaddr_in addr;

  27. socklen_t socklen = sizeof(struct sockaddr_in);

  28. memset(&addr , 0 , sizeof(struct sockaddr_in));

  29. addr.sin_family = AF_INET;

  30. addr.sin_addr.s_addr = inet_addr(ip);

  31. addr.sin_port = htons(port);

  32.  
  33. //创建描述符

  34. int fd = socket( AF_INET , SOCK_STREAM , 0);

  35. if (fd < 0){

  36. printf("socket() error\n");

  37. return -1;

  38. }

  39. //设置描述符为非阻塞

  40. setnonblock(fd);

  41.  
  42. //连接

  43. int res;

  44. res = connect(fd , (struct sockaddr*)&addr , socklen);

  45. if (res == 0){

  46. printf("connect ok(1)\n");

  47. } else if (res == -1 && errno != EINPROGRESS){

  48. printf("connect err(1)\n");

  49. close(fd);

  50. return -1;

  51. } else {

  52. int epfd;

  53. //创建epoll描述符

  54. epfd = epoll_create(1024);

  55. if ( (epfd = epoll_create(1024) ) == -1){

  56. printf("epoll_create() err\n");

  57. close(fd);

  58. return -1;

  59. }

  60. //添加关注事件

  61. struct epoll_event ev;

  62. ev.events = EPOLLOUT;

  63. ev.data.fd = fd;

  64. epoll_ctl( epfd , EPOLL_CTL_ADD , fd , &ev);

  65.  
  66. //编写网络程序的时候,epoll是程序的主循环.我们这里为了测试,连接上或connect超时(75秒)就break掉.

  67. //正常的流程是写一个处理connect结果的回调函数.

  68. int event_max = 1;

  69. struct epoll_event events[event_max];

  70. int i;

  71. while (1){

  72. res = epoll_wait( epfd , events , event_max , -1);

  73. if (res > 0){

  74. for ( i = 0 ; i < res ; i++){

  75. if ( events[i].data.fd == fd && ( events[i].events & EPOLLOUT) ){ //29(EPOLLOUT|EPOLLERR|EPOLLHUP) //4(EPOLLOUT)

  76. //检查是否连接成功

  77. int optval;

  78. socklen_t optlen = sizeof(optval);

  79. int res1 = getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen);

  80. if ( res1 < 0 || optval){

  81. close(fd);

  82. close(epfd);

  83. printf("connect err(2)\n");

  84. return -1;

  85. } else {

  86. printf("connect ok(2)\n");

  87. }

  88. }

  89. }

  90. break;

  91. }

  92. }

  93. close(fd);

  94. close(epfd);

  95. }

  96. }

10.select函数可以检测网络异常吗?

答:不可以。当网络异常时,select函数可以检测到可读事件,这时候用read函数读取数据,会返回0.

详情见UNP 卷1 6.3.1 select描述符就绪条件 第131页和132页

 

12. epoll的水平模式LT和边缘模式ET

答:默认是LT。

水平模式指的是低电平到低电平,或者高电平到高电平。

边缘模式指的是低电平到高电平,或者高电平到低电平。

水平模式:如果epoll_wait检测到可读事件,可以一次性把数据读完,也可以分多次读完。

边缘模式:如果epoll_wait检测到可读事件,必须一次性把数据读完,否则如果分两次读,第一次读部分,第二次再读时,没有可读触发信号了,读不到了。只能使用非阻塞的网络模型。

参考博客:http://blog.csdn.net/analogous_love/article/details/60761528

用于windows或linux水平模式下收取数据,这种情况下收取的数据可以小于指定大小,总之一次能收到多少是多少:

 
  1. bool TcpSession::Recv()

  2. {

  3. //每次只收取256个字节

  4. char buff[256];

  5. //memset(buff, 0, sizeof(buff));

  6. int nRecv = ::recv(clientfd_, buff, 256, 0);

  7. if (nRecv == 0)

  8. return false;

  9.  
  10. inputBuffer_.add(buff, (size_t)nRecv);

  11.  
  12. return true;

  13. }

如果是linux epoll边缘模式(ET),则一定要一次性收完:

 
  1. bool TcpSession::RecvEtMode()

  2. {

  3. //每次只收取256个字节

  4. char buff[256];

  5. while (true)

  6. {

  7. //memset(buff, 0, sizeof(buff));

  8. int nRecv = ::recv(clientfd_, buff, 256, 0);

  9. if (nRecv == -1)

  10. {

  11. if (errno == EWOULDBLOCK || errno == EINTR)

  12. return true;

  13.  
  14. return false;

  15. }

  16. //对端关闭了socket

  17. else if (nRecv == 0)

  18. return false;

  19.  
  20. inputBuffer_.add(buff, (size_t)nRecv);

  21. }

  22.  
  23. return true;

  24. }

13. 如何将socket设置成非阻塞的(创建时设置与创建完成后设置),非阻塞socket与阻塞的socket在收发数据上的区别

14. send/recv(read/write)返回值大于0、等于0、小于0的区别

答:

recv:
阻塞与非阻塞recv返回值没有区分,都是 <0:出错,=0:连接关闭,>0接收到数据大小,
特别:非阻塞模式下返回 值 <0时并且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况 下认为连接是正常的,继续接收。
只是阻塞模式下recv会阻塞着接收数据,非阻塞模式下如果没有数据会返回,不会阻塞着读,因此需要 循环读取。
write:
阻塞与非阻塞write返回值没有区分,都是 <0:出错,=0:连接关闭,>0发送数据大小,
特别:非阻塞模式下返回值 <0时并且 (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认为连接是正常的, 继续发送。
只是阻塞模式下write会阻塞着发送数据,非阻塞模式下如果暂时无法发送数据会返回,不会阻塞着 write,因此需要循环发送。
read:
阻塞与非阻塞read返回值没有区分,都是 <0:出错,=0:连接关闭,>0接收到数据大小,
特别:非阻塞模式下返回 值 <0时并且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况 下认为连接是正常的,继续接收。
只是阻塞模式下read会阻塞着接收数据,非阻塞模式下如果没有数据会返回,不会阻塞着读,因此需要 循环读取。
send:
阻塞与非阻塞send返回值没有区分,都是 <0:出错,=0:连接关闭,>0发送数据大小,
特别:非阻塞模式下返回值 <0时并且 (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认为连接是正常的, 继续发送。
只是阻塞模式下send会阻塞着发送数据,非阻塞模式下如果暂时无法发送数据会返回,不会阻塞着 send,因此需要循环发送。

 

15.如何编写正确的收数据代码与发数据代码

答:都需要考虑缓冲区的设计,发数据如果失败先缓存起来,等待下一次的机会再发。

参考博客:http://blog.csdn.net/analogous_love/article/details/60761528

 
  1. bool TcpSession::Send()

  2. {

  3. while (true)

  4. {

  5. int n = ::send(clientfd_, buffer_, buffer_.length(), 0);

  6. if (n == -1)

  7. {

  8. //tcp窗口容量不够, 暂且发不出去,下次再发

  9. if (errno == EWOULDBLOCK)

  10. break;

  11. //被信号中断,继续发送

  12. else if (errno == EINTR)

  13. continue;

  14.  
  15. return false;

  16. }

  17. //对端关闭了连接

  18. else if (n == 0)

  19. return false;

  20.  
  21. buffer_.erase(n);

  22. //全部发送完毕

  23. if (buffer_.length() == 0)

  24. break;

  25. }

  26.  
  27. return true;

  28. }

tcp是流协议,应用层要自己来区分包的边界,就是加包头包尾。但是tcp有个滑动窗口,假如是10,我第一个数据包占了6,然后在服务器还没读取的时候,我发送第二个,也是6大小,此时会先只发4过去,剩下的6-4=2就得等服务端腾出空间后并提示后再发了.

 

16.发送数据缓冲区与接收数据缓冲区如何设计

答:参考学习muduo的buffer设计

 

17.socket选项SO_SNDTIMEO和SO_RCVTIMEO

答:阻塞模式时需要设置超时时间,否则会卡死。

 

18.socket选项TCP_NODELAY

答:一般来说,应用层send函数不会立刻把数据发出去,而是先给到网卡缓冲区。网卡缓冲区需要等待数据积累到一定量之后才会发送数据,这样会导致一定的延迟。

默认情况下,发送数据采用Nagle算法。这样虽然提高了网络吞吐量,但是实时性却降低了,在一些交互性很强的应用程序来说是不允许的,使用TCP_NODELAY选项可以禁止Nagle算法,避免连续发包出现延迟,这对低延迟网络服务很重要。 此时,应用程序向内核递交的每个数据包都会立即发送出去。需要注意的是,虽然禁止了Nagle 算法,但网络的传输仍然受到TCP确认延迟机制的影响。

 

19.socket选项SO_REUSEADDR和SO_REUSEPORT(Windows平台与linux平台的区别)

答:说白了当服务器进程关闭时,想立刻再复用原来的ip和端口需要等待2MSL的时间。举个例子,服务器监听了127.0.0.1和8001端口,如果此时结束掉进程,再立刻重启,是不可以再监听成功的。因为TCP四次挥手最后一步TIME_WAIT需要等待应答,如果等不到需要重连。

MSL的时间一般是1min~4min不等。MSL是数据包的最大存活时间,最后一步的ack需要考虑去和回,所以周期是2*MSL。

Linux是所有进程在2MSL的时间内不能复用刚才使用的ip和port,bind会失败;Windows是除了本进程可以,其他进程不可以。操作系统这么设计的。

结论:一般为了方便重启服务器或调试,会设置这两个选项,REUSE就是复用的意思,让进程立刻可以复用地址和端口。

 

20.socket选项SO_LINGER

答:当调用closesocket关闭套接字时,SO_LINGER将决定系统如何处理残存在套接字发送队列中的数据。处理方式无非两种:丢弃或者将数据继续发送至对端,优雅关闭连接。事实上,SO_LINGER并不被推荐使用,大多数情况下我们推荐使用默认的关闭方式(即下方表格中的第一种情况)。

 

21.shutdown与优雅关闭

答:socket 多进程中的shutdown, close使用
当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据操作:close(sockfd);
你也可以调用shutdown()函数来关闭该socket。该函数允许你只停止在某个方向上的数据传输,而一个方向上的数据传输继续进行。如你可以关闭某socket的写操作而允许继续在该socket上接受数据,直至读入所有数据。
int shutdown(int sockfd,int how);
Sockfd是需要关闭的socket的描述符。参数 how允许为shutdown操作选择以下几种方式:
    SHUT_RD:关闭连接的读端。也就是该套接字不再接受数据,任何当前在套接字接受缓冲区的数据将被丢弃。进程将不能对该套接字发出任何读操作。对TCP套接字该调用之后接受到的任何数据将被确认然后无声的丢弃掉。
    SHUT_WR:关闭连接的写端,进程不能在对此套接字发出写操作。
    SHUT_RDWR:相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR。

服务器如果要主动关闭连接,可以这么执行:先关本地“写”端,等对方关闭后,再关本地“读”端。

服务器如果要被动关闭连接,可以这么执行:当read函数返回值是0时,先关本地“写”端,等对方关闭后,再关本地“读”端。

 

23.socket选项SO_KEEPALIVE

答:一般来说不推荐使用默认的心跳机制,默认是2小时。默认心跳有两个缺陷:

1、貌似设置之后会影响整个操作系统所有应用层的心跳时间;

2、每2小时发一次心跳,有时候会造成流量浪费。比如应用层如果有正常数据交互,不需要发心跳。

具体实现可以参考redis源码anet.c里的anetKeepAlive函数。

 

24.关于错误码EINTR

答:EINTR是linux中函数的返回状态,在不同的函数中意义不同。表示某种阻塞的操作,被接收到的信号中断,造成的一种错误返回值。
write
表示:由于信号中断,没写成功任何数据。
read
表示:由于信号中断,没读到任何数据。
sem_wait
函数调用被信号处理函数中断。
recv
由于信号中断返回,没有任何数据可用。

 

25.如何解决tcp粘包问题

答:通过应用层自定义协议来解决

1、固定长度的包

2、每个包以"\r\n"结尾

3、定义结构体,包含固定包头,包体长度等

 

26.信号SIGPIPE与EPIPE错误码

答:在linux下写socket的程序的时候,如果服务器尝试send到一个disconnected socket上,就会让底层抛出一个SIGPIPE信号。 这个信号的缺省处理方法是退出进程,大多数时候这都不是我们期望的。也就是说,当服务器繁忙,没有及时处理客户端断开连接的事件,就有可能出现在连接断开之后继续发送数据的情况,如果对方断开而本地继续写入的话,就会造成服务器进程意外退出。

根据信号的默认处理规则SIGPIPE信号的默认执行动作是terminate(终止、退出),所以client会退出。若不想客户端退出可以把 SIGPIPE设为SIG_IGN 如:signal(SIGPIPE, SIG_IGN); 这时SIGPIPE交给了系统处理。 服务器采用了fork的话,要收集垃圾进程,防止僵尸进程的产生,可以这样处理: signal(SIGCHLD,SIG_IGN); 交给系统init去回收。 这里子进程就不会产生僵尸进程了。

 

27.gethostbyname阻塞与错误码获取问题

答:Unix/Linux下的gethostbyname函数常用来向DNS查询一个域名的IP地址。 由于DNS的递归查询,常常会发生gethostbyname函数在查询一个域名时严重超时。而该函数又不能像connect和read等函数那样通过setsockopt或者select函数那样设置超时时间,因此常常成为程序的瓶颈。有人提出一种解决办法是用alarm设置定时信号,如果超时就用setjmp和longjmp跳过gethostbyname函数(这种方式我没有试过,不知道具体效果如何)。
gethostbyname确实是阻塞的,但应该可以设置一个time_out免得DNS Server出问题时老是执行,关于设置Time_out,参阅一下code:

 
  1. int timeout = TIMEOUT_VALUE;

  2. int err;

  3. SOCKET s;

  4. s = socket( ... );

  5. err = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout).

在使用 gethostbyname() 的时候,你不能用perror() 打印错误信息 (因为 errno 没有使用),你应该调用 herror()。herror()函数签名如下:
void herror(const char *s);

举例如下:参考博客http://blog.csdn.net/analogous_love/article/details/53433994

 
  1. bool Connect(const char* pszIp, int nPort)

  2. {

  3. struct hostent* pHostent = NULL;

  4. char* host = NULL;

  5.  
  6. struct sockaddr_in addrSrv;

  7. memset(&addrSrv, 0, sizeof(addrSrv));

  8. addrSrv.sin_addr.s_addr = ::inet_addr(pszIp);

  9. if (addrSrv.sin_addr.s_addr == INADDR_NONE)

  10. {

  11. pHostent = ::gethostbyname(pszIp);

  12. if (pHostent != NULL)

  13. {

  14. host = inet_ntoa(*((struct in_addr *)pHostent->h_addr));

  15. std::cout << pszIp << "=" << host << std::endl;

  16. }

  17. else

  18. {

  19. herror("gethostbyname error");

  20. std::cout << std::endl;

  21. return false;

  22. }

  23. }

  24. else

  25. host = (char*)pszIp;

  26.  
  27. if (Socket.Connect(host, nPort))

  28. return true;

  29.  
  30. std::cout << "Unable to connect to server " << pszIp << ":" << nPort << std::endl;

  31. return false;

  32. }

28.心跳包的设计技巧(保活心跳包与业务心跳包)

答:http://blog.csdn.net/analogous_love/article/details/78388187

 

29.客户端断线重连机制如何设计

答:客户端先2s连接一次服务器,如果失败,再4s连接一次,如果失败,再8s连接一次,如果失败再16s连接一次。。。。。

补充一个情况,当网络状况突变时,立刻连接一次。例如,用户从地铁站出来,手机信号满格了,此时手机app立刻连接服务器。

《Linux多线程服务端编程:使用muduo C++网络库》P333有这么描述:

客户端连接断开后初次重试的延迟应该有随机性,比如说服务端奔溃,它所有的客户连接同时断开,然后0.5s之后再次发起连接,这样既可能造成SYN丢包,也可能给服务器带来短期大负载,影响其服务质量。因此每个客户端应该等待一段随机的时间(0.5~2s),再重试,避免拥塞。

 

30.如何检测对端已经关闭socket

答:根据ERRNO和recv结果进行判断
在UNIX/LINUX下,非阻塞模式SOCKET可以采用recv+MSG_PEEK的方式进行判断,其中MSG_PEEK保证了仅仅进行状态判断,而不影响数据接收
对于主动关闭的SOCKET, recv返回-1,而且errno被置为9(#define EBADF   9 /* Bad file number */)或104 (#define ECONNRESET 104 /* Connection reset by peer */)
对于被动关闭的SOCKET,recv返回0,而且errno被置为11(#define EWOULDBLOCK EAGAIN /* Operation would block */)
 对正常的SOCKET, 如果有接收数据,则返回>0, 否则返回-1,而且errno被置为11(#define EWOULDBLOCK EAGAIN /* Operation would block */)
因此对于简单的状态判断(不过多考虑异常情况):
    recv返回>0,   正常

 

31.如何清除无效的死链(端与端之间的线路故障)

答:TCP四次挥手时产生的TIME_WAIT或CLOSE_WAIT,造成死链。或者服务器A<->路由器B<->路由器C<->客户端D,链路中的路由器发生了故障,造成死链。需要采取定时器/心跳检测来清理死链。

 

32.定时器的不同实现及优缺点

答:Windows可以使用OnTimer函数,Linux网络库定时器需要自己实现。

例如libevent的小根堆,libuv的红黑树,muduo的二叉搜索树,nginx的红黑树,redis的升序链表等

学习redis网络库,muduo,优先队列std:priority_queue

 

34.http协议的具体格式

35.http head、get与post方法的细节

答:

 
  1. GET /index.php HTTP/1.1\r\n

  2. Host: www.hootina.org\r\n

  3. Connection: keep-alive\r\n

  4. Cache-Control: max-age=0\r\n

  5. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n

  6. content-length: 8

  7. User-Agent: Mozilla/5.0\r\n

  8. \r\n

  9. abcdefgh

  10. POST /index.php HTTP/1.1\r\n

  11. Host: www.hootina.org\r\n

  12. Connection: keep-alive\r\n

  13. Cache-Control: max-age=0\r\n

  14. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n

  15. content-length: 8

  16. User-Agent: Mozilla/5.0\r\n

  17. \r\n

  18. abcdefgh

36.http代理、socks4代理与socks5代理如何编码实现

答:代码如下

 
  1. BOOL UMySocket::Connect(PNETCONN_INFO pInfo,LPSTR lpMessage) //连接服务器

  2. {

  3. wsysplus_memory vMemory;

  4. long noDelay(1),tmSend(1800*1000L),tmRecv(1800*1000L);

  5. LPSTR lpBuffer=vMemory.GetBuf(8001);

  6. //关闭连接先

  7. UMySocket::Close();

  8. //初始化

  9. if(!lpBuffer)

  10. {

  11. if(lpMessage) strcpy(lpMessage,"MALLOC DATA");

  12. return(FALSE);

  13. }

  14. _hSocket=socket(PF_INET,SOCK_STREAM,0);

  15. if(_hSocket==INVALID_SOCKET)

  16. {

  17. if(lpMessage) strcpy(lpMessage,"INVALID_SOCKET");

  18. return(FALSE);

  19. }

  20. //设置连接属性

  21. setsockopt(_hSocket,IPPROTO_TCP,TCP_NODELAY,(LPSTR)&noDelay,sizeof(long));

  22. setsockopt(_hSocket,SOL_SOCKET,SO_SNDTIMEO,(LPSTR)&tmSend,sizeof(long));

  23. setsockopt(_hSocket,SOL_SOCKET,SO_RCVTIMEO,(LPSTR)&tmRecv,sizeof(long));

  24. //连接服务器

  25. WORD wPortConn=(WORD)pInfo->proxyport;

  26. char *lpServer=pInfo->proxyurl;

  27. sockaddr_in remote={0};

  28. remote.sin_family = AF_INET;

  29. if(!(pInfo->conntype&0x000F)) //未使用代理

  30. {

  31. lpServer = pInfo->srvurl;

  32. wPortConn = (WORD)pInfo->srvport;

  33. }

  34.  
  35. remote.sin_port = htons(wPortConn);

  36. LPHOSTENT lphost=NULL;

  37. if((remote.sin_addr.s_addr=inet_addr(lpServer))==INADDR_NONE)

  38. {

  39. if(lphost=gethostbyname(lpServer))

  40. remote.sin_addr.s_addr = ((LPIN_ADDR)lphost->h_addr)->s_addr;

  41. }

  42. do

  43. {

  44. if(connect(_hSocket,(sockaddr*)&remote,sizeof(remote))

  45. ==SOCKET_ERROR&&WSAGetLastError()!=WSAEWOULDBLOCK)

  46. {

  47. if(lpMessage) sprintf(lpMessage,"服务器:%s:%d连接失败",lpServer,wPortConn);

  48. break;

  49. }

  50. if(pInfo->conntype&PROXY_HTTP)//http proxy

  51. {

  52. sprintf(lpBuffer,"CONNECT %s:%d HTTP/1.0\r\nUser-Agent:rmtcmd/0.1\r\n\r\n",

  53. pInfo->srvurl,pInfo->srvport);

  54. send(_hSocket,lpBuffer,strlen(lpBuffer),0);

  55. if(recv(_hSocket,lpBuffer,8000,0)<1)

  56. {

  57. if(lpMessage) sprintf(lpMessage,"代理服务器:%s:%d通讯失败",pInfo->srvurl,pInfo->srvport);

  58. break;

  59. }

  60. if(strstr(lpBuffer,"Connection established")==NULL)

  61. {

  62. if(lpMessage) sprintf(lpMessage,"代理服务器:%s:%d通讯失败",pInfo->srvurl,pInfo->srvport);

  63. break;

  64. }

  65. }

  66. else if(pInfo->conntype&PROXY_SOCK5) //sock5 proxy

  67. {

  68. PSOCK5REQ req = (PSOCK5REQ)lpBuffer;

  69. PSOCK5ANS ans = (PSOCK5ANS)(lpBuffer+1024);

  70. req->ver = 5;

  71. req->lmethods = 2;

  72. req->methods[0] = 0;

  73. req->methods[1] = 2;

  74. send(_hSocket,(LPSTR)req,4,0);

  75. if(!Recv(ans,sizeof(SOCK5ANS))||

  76. ans->ver!=5||(ans->method&&ans->method!=2))

  77. {

  78. if(lpMessage) sprintf(lpMessage,"代理服务器:%s:%d通讯失败",pInfo->srvurl,pInfo->srvport);

  79. break;

  80. }

  81. if(ans->method==2) //need user & passwd

  82. {

  83. PAUTHREQ reqa = (PAUTHREQ)lpBuffer;

  84. PAUTHANS ansa = (PAUTHANS)(lpBuffer+1024);

  85. memset(reqa,0,sizeof(AUTHREQ));

  86. reqa->ver = 1;

  87. reqa->ulen = strlen(strcpy(reqa->user,pInfo->proxyuser));

  88. reqa->plen = strlen(strcpy(reqa->passwd,pInfo->proxypasswd));

  89. send(_hSocket,lpBuffer,sizeof(AUTHREQ),0);

  90. if(!Recv(ansa,sizeof(AUTHANS))||ansa->ver!=1||ansa->status)

  91. {

  92. if(lpMessage) sprintf(lpMessage,"代理服务器:%s:%d通讯失败",pInfo->srvurl,pInfo->srvport);

  93. break;

  94. }

  95. }

  96. PSOCK5REQEX reqex = (PSOCK5REQEX)lpBuffer;

  97. PSOCK5ANSEX ansex = (PSOCK5ANSEX)(lpBuffer+1024);

  98. memset(reqex,0,sizeof(SOCK5REQEX));

  99. reqex->ver = 5;

  100. reqex->cmd = 1;

  101. reqex->rsv = 0;

  102. reqex->atyp = 1;

  103. if((reqex->addr=inet_addr(pInfo->srvurl))==INADDR_NONE)

  104. {

  105. if(lphost=gethostbyname(pInfo->srvurl))

  106. reqex->addr = ((LPIN_ADDR)lphost->h_addr)->s_addr;

  107. }

  108. reqex->port = ntohs((WORD)pInfo->srvport);

  109. send(_hSocket,(LPSTR)reqex,sizeof(SOCK5REQEX),0);

  110. if(!Recv(ansex,sizeof(SOCK5ANSEX))||ansex->ver!=5||ansex->rep)

  111. {

  112. if(lpMessage) sprintf(lpMessage,"代理服务器:%s:%d通讯失败",pInfo->srvurl,pInfo->srvport);

  113. break;

  114. }

  115. }

  116. else if(pInfo->conntype&PROXY_SOCK4) //sock4 proxy

  117. {

  118. PSOCK4REQ req=(PSOCK4REQ)lpBuffer;

  119. PSOCK4ANS ans=(PSOCK4ANS)(lpBuffer+1024);

  120. req->vn = 4;

  121. req->cd = 1;

  122. req->port = ntohs((WORD)pInfo->srvport);

  123. if((req->addr=inet_addr(pInfo->srvurl))==INADDR_NONE)

  124. {

  125. if(lphost=gethostbyname(pInfo->srvurl))

  126. req->addr = ((LPIN_ADDR)lphost->h_addr)->s_addr;

  127. }

  128. send(_hSocket,lpBuffer,sizeof(SOCK4REQ),0);

  129. if(!Recv(ans,sizeof(SOCK4ANS)))

  130. {

  131. if(lpMessage) sprintf(lpMessage,"代理服务器:%s:%d通讯失败",pInfo->srvurl,pInfo->srvport);

  132. break;

  133. }

  134. if(ans->vn||ans->cd!=90)

  135. {

  136. if(lpMessage) sprintf(lpMessage,"代理服务器:%s:%d通讯失败",pInfo->srvurl,pInfo->srvport);

  137. break;

  138. }

  139. }

  140. return(TRUE);

  141. }while(0);

  142. UMySocket::Close();

  143. return(FALSE);

  144. }

  145.  
  146. //代理服务器连接

  147. #pragma pack(push,1)

  148. //sock4 req & ans

  149. typedef struct tagSock4Req

  150. {

  151. char vn;

  152. char cd;

  153. WORD port;

  154. DWORD addr;

  155. char other[1];

  156. }SOCK4REQ,*PSOCK4REQ;

  157.  
  158. typedef struct tagSock4Ans

  159. {

  160. char vn;

  161. char cd;

  162. }SOCK4ANS,*PSOCK4ANS;

  163. //sock5 req & ans

  164. typedef struct tagSock5Req

  165. {

  166. char ver;

  167. char lmethods;

  168. char methods[255];

  169. }SOCK5REQ,*PSOCK5REQ;

  170.  
  171. typedef struct tagSock5Ans

  172. {

  173. char ver;

  174. char method;

  175. }SOCK5ANS,*PSOCK5ANS;

  176. //sock5 check user

  177. typedef struct tagAuthReq

  178. {

  179. char ver;

  180. char ulen;

  181. char user[255];

  182. char plen;

  183. char passwd[255];

  184. }AUTHREQ,*PAUTHREQ;

  185.  
  186. typedef struct tagAuthAns

  187. {

  188. char ver;

  189. char status;

  190. }AUTHANS,*PAUTHANS;

  191.  
  192. typedef struct tagSock5ReqEx

  193. {

  194. char ver;

  195. char cmd;

  196. char rsv;

  197. char atyp;

  198. long addr;

  199. WORD port;

  200. }SOCK5REQEX,*PSOCK5REQEX;

  201.  
  202. typedef struct tagSock5AnsEx

  203. {

  204. char ver;

  205. char rep;

  206. char rsv;

  207. char atyp;

  208. char other[1];

  209. }SOCK5ANSEX,*PSOCK5ANSEX;

  210. #pragma pack(pop)

37.ping

38.telnet

 

39.close函数,fork

答:参考《UNP》卷1,第94页。close是引用计数-1,在没有到0的时候是不会关闭套接字的。fork调用会使父进程打开的socket引用计数+1,。所以一般多进程里面,父进程在fork子进程之后,父进程可以关闭accept套接字,子进程可以关闭listen套接字。这个时候两个套接字计数都从2减到1,所以不会关闭。所以父进程可以只做监听,子进程只做通信。

 

40.Linux终端调试命令

netstat -nalp|grep 8011 #查看8011端口的连接情况,观察TCP状态图
netstat -nalp|grep 8011|wc -l #查看8011端口的客户端连接数
ulimit -n 102400 #修改当前进程的最大文件数

tcpdump -i any 'tcp port 80'

lsof -i -Pn #lsof是list opened fd的单词缩写

netstat -anip

 

41.Windows cmd命令

netstat -ano|findstr "8011"#查看8011端口的连接情况,观察TCP状态图

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值