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
https://blog.csdn.net/bianchengxiaosheng/article/details/78750237CLOSE_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
//关于tcp连接的异步connect实现流程如下:
//(1)设置socket连接为非阻塞.
//(2)调用connect函数.返回0表明连接成功.如果返回-1,同时errno为EINPROGRESS表明正在建立连接.
//(3)使用select , epoll等 , 当描述符可写的时候检查连接状态.
#include
#include
#include
#include
#include
#include
#include
#include
void setnonblock(int fd)
{
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
int main(){
const char* ip = "127.0.0.1";
short port = 9999;
//设置连接地址
struct sockaddr_in addr;
socklen_t socklen = sizeof(struct sockaddr_in);
memset(&addr , 0 , sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip);
addr.sin_port = htons(port);
//创建描述符
int fd = socket( AF_INET , SOCK_STREAM , 0);
if (fd < 0){
printf("socket() error\n");
return -1;
}
//设置描述符为非阻塞
setnonblock(fd);
//连接
int res;
res = connect(fd , (struct sockaddr*)&addr , socklen);
if (res == 0){
printf("connect ok(1)\n");
} else if (res == -1 && errno != EINPROGRESS){
printf("connect err(1)\n");
close(fd);
return -1;
} else {
int epfd;
//创建epoll描述符
epfd = epoll_create(1024);
if ( (epfd = epoll_create(1024) ) == -1){
printf("epoll_create() err\n");
close(fd);
return -1;
}
//添加关注事件
struct epoll_event ev;
ev.events = EPOLLOUT;
ev.data.fd = fd;
epoll_ctl( epfd , EPOLL_CTL_ADD , fd , &ev);
//编写网络程序的时候,epoll是程序的主循环.我们这里为了测试,连接上或connect超时(75秒)就break掉.
//正常的流程是写一个处理connect结果的回调函数.
int event_max = 1;
struct epoll_event events[event_max];
int i;
while (1){
res = epoll_wait( epfd , events , event_max , -1);
if (res > 0){
for ( i = 0 ; i < res ; i++){
if ( events[i].data.fd == fd && ( events[i].events & EPOLLOUT) ){ //29(EPOLLOUT|EPOLLERR|EPOLLHUP) //4(EPOLLOUT)
//检查是否连接成功
int optval;
socklen_t optlen = sizeof(optval);
int res1 = getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen);
if ( res1 < 0 || optval){
close(fd);
close(epfd);
printf("connect err(2)\n");
return -1;
} else {
printf("connect ok(2)\n");
}
}
}
break;
}
}
close(fd);
close(epfd);
}
}
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水平模式下收取数据,这种情况下收取的数据可以小于指定大小,总之一次能收到多少是多少:
bool TcpSession::Recv()
{
//每次只收取256个字节
char buff[256];
//memset(buff, 0, sizeof(buff));
int nRecv = ::recv(clientfd_, buff, 256, 0);
if (nRecv == 0)
return false;
inputBuffer_.add(buff, (size_t)nRecv);
return true;
}
如果是linux epoll边缘模式(ET),则一定要一次性收完:
bool TcpSession::RecvEtMode()
{
//每次只收取256个字节
char buff[256];
while (true)
{
//memset(buff, 0, sizeof(buff));
int nRecv = ::recv(clientfd_, buff, 256, 0);
if (nRecv == -1)
{
if (errno == EWOULDBLOCK || errno == EINTR)
return true;
return false;
}
//对端关闭了socket
else if (nRecv == 0)
return false;
inputBuffer_.add(buff, (size_t)nRecv);
}
return true;
}
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
bool TcpSession::Send()
{
while (true)
{
int n = ::send(clientfd_, buffer_, buffer_.length(), 0);
if (n == -1)
{
//tcp窗口容量不够, 暂且发不出去,下次再发
if (errno == EWOULDBLOCK)
break;
//被信号中断,继续发送
else if (errno == EINTR)
continue;
return false;
}
//对端关闭了连接
else if (n == 0)
return false;
buffer_.erase(n);
//全部发送完毕
if (buffer_.length() == 0)
break;
}
return true;
}
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:
int timeout = TIMEOUT_VALUE;
int err;
SOCKET s;
s = socket( ... );
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
bool Connect(const char* pszIp, int nPort)
{
struct hostent* pHostent = NULL;
char* host = NULL;
struct sockaddr_in addrSrv;
memset(&addrSrv, 0, sizeof(addrSrv));
addrSrv.sin_addr.s_addr = ::inet_addr(pszIp);
if (addrSrv.sin_addr.s_addr == INADDR_NONE)
{
pHostent = ::gethostbyname(pszIp);
if (pHostent != NULL)
{
host = inet_ntoa(*((struct in_addr *)pHostent->h_addr));
std::cout << pszIp << "=" << host << std::endl;
}
else
{
herror("gethostbyname error");
std::cout << std::endl;
return false;
}
}
else
host = (char*)pszIp;
if (Socket.Connect(host, nPort))
return true;
std::cout << "Unable to connect to server " << pszIp << ":" << nPort << std::endl;
return false;
}
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方法的细节
答:
GET /index.php HTTP/1.1\r\n
Host: www.hootina.org\r\n
Connection: keep-alive\r\n
Cache-Control: max-age=0\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n
content-length: 8
User-Agent: Mozilla/5.0\r\n
\r\n
abcdefgh
POST /index.php HTTP/1.1\r\n
Host: www.hootina.org\r\n
Connection: keep-alive\r\n
Cache-Control: max-age=0\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n
content-length: 8
User-Agent: Mozilla/5.0\r\n
\r\n
abcdefgh
36.http代理、socks4代理与socks5代理如何编码实现
答:代码如下
BOOL UMySocket::Connect(PNETCONN_INFO pInfo,LPSTR lpMessage) //连接服务器
{
wsysplus_memory vMemory;
long noDelay(1),tmSend(1800*1000L),tmRecv(1800*1000L);
LPSTR lpBuffer=vMemory.GetBuf(8001);
//关闭连接先
UMySocket::Close();
//初始化
if(!lpBuffer)
{
if(lpMessage) strcpy(lpMessage,"MALLOC DATA");
return(FALSE);
}
_hSocket=socket(PF_INET,SOCK_STREAM,0);
if(_hSocket==INVALID_SOCKET)
{
if(lpMessage) strcpy(lpMessage,"INVALID_SOCKET");
return(FALSE);
}
//设置连接属性
setsockopt(_hSocket,IPPROTO_TCP,TCP_NODELAY,(LPSTR)&noDelay,sizeof(long));
setsockopt(_hSocket,SOL_SOCKET,SO_SNDTIMEO,(LPSTR)&tmSend,sizeof(long));
setsockopt(_hSocket,SOL_SOCKET,SO_RCVTIMEO,(LPSTR)&tmRecv,sizeof(long));
//连接服务器
WORD wPortConn=(WORD)pInfo->proxyport;
char *lpServer=pInfo->proxyurl;
sockaddr_in remote={0};
remote.sin_family = AF_INET;
if(!(pInfo->conntype&0x000F)) //未使用代理
{
lpServer = pInfo->srvurl;
wPortConn = (WORD)pInfo->srvport;
}
remote.sin_port = htons(wPortConn);
LPHOSTENT lphost=NULL;
if((remote.sin_addr.s_addr=inet_addr(lpServer))==INADDR_NONE)
{
if(lphost=gethostbyname(lpServer))
remote.sin_addr.s_addr = ((LPIN_ADDR)lphost->h_addr)->s_addr;
}
do
{
if(connect(_hSocket,(sockaddr*)&remote,sizeof(remote))
==SOCKET_ERROR&&WSAGetLastError()!=WSAEWOULDBLOCK)
{
if(lpMessage) sprintf(lpMessage,"服务器:%s:%d连接失败",lpServer,wPortConn);
break;
}
if(pInfo->conntype&PROXY_HTTP)//http proxy
{
sprintf(lpBuffer,"CONNECT %s:%d HTTP/1.0\r\nUser-Agent:rmtcmd/0.1\r\n\r\n",
pInfo->srvurl,pInfo->srvport);
send(_hSocket,lpBuffer,strlen(lpBuffer),0);
if(recv(_hSocket,lpBuffer,8000,0)<1)
{
if(lpMessage) sprintf(lpMessage,"代理服务器:%s:%d通讯失败",pInfo->srvurl,pInfo->srvport);
break;
}
if(strstr(lpBuffer,"Connection established")==NULL)
{
if(lpMessage) sprintf(lpMessage,"代理服务器:%s:%d通讯失败",pInfo->srvurl,pInfo->srvport);
break;
}
}
else if(pInfo->conntype&PROXY_SOCK5) //sock5 proxy
{
PSOCK5REQ req = (PSOCK5REQ)lpBuffer;
PSOCK5ANS ans = (PSOCK5ANS)(lpBuffer+1024);
req->ver = 5;
req->lmethods = 2;
req->methods[0] = 0;
req->methods[1] = 2;
send(_hSocket,(LPSTR)req,4,0);
if(!Recv(ans,sizeof(SOCK5ANS))|| ans->ver!=5||(ans->method&&ans->method!=2))
{
if(lpMessage) sprintf(lpMessage,"代理服务器:%s:%d通讯失败",pInfo->srvurl,pInfo->srvport);
break;
}
if(ans->method==2) //need user & passwd
{
PAUTHREQ reqa = (PAUTHREQ)lpBuffer;
PAUTHANS ansa = (PAUTHANS)(lpBuffer+1024);
memset(reqa,0,sizeof(AUTHREQ));
reqa->ver = 1;
reqa->ulen = strlen(strcpy(reqa->user,pInfo->proxyuser));
reqa->plen = strlen(strcpy(reqa->passwd,pInfo->proxypasswd));
send(_hSocket,lpBuffer,sizeof(AUTHREQ),0);
if(!Recv(ansa,sizeof(AUTHANS))||ansa->ver!=1||ansa->status)
{
if(lpMessage) sprintf(lpMessage,"代理服务器:%s:%d通讯失败",pInfo->srvurl,pInfo->srvport);
break;
}
}
PSOCK5REQEX reqex = (PSOCK5REQEX)lpBuffer;
PSOCK5ANSEX ansex = (PSOCK5ANSEX)(lpBuffer+1024);
memset(reqex,0,sizeof(SOCK5REQEX));
reqex->ver = 5;
reqex->cmd = 1;
reqex->rsv = 0;
reqex->atyp = 1;
if((reqex->addr=inet_addr(pInfo->srvurl))==INADDR_NONE)
{
if(lphost=gethostbyname(pInfo->srvurl))
reqex->addr = ((LPIN_ADDR)lphost->h_addr)->s_addr;
}
reqex->port = ntohs((WORD)pInfo->srvport);
send(_hSocket,(LPSTR)reqex,sizeof(SOCK5REQEX),0);
if(!Recv(ansex,sizeof(SOCK5ANSEX))||ansex->ver!=5||ansex->rep)
{
if(lpMessage) sprintf(lpMessage,"代理服务器:%s:%d通讯失败",pInfo->srvurl,pInfo->srvport);
break;
}
}
else if(pInfo->conntype&PROXY_SOCK4) //sock4 proxy
{
PSOCK4REQ req=(PSOCK4REQ)lpBuffer;
PSOCK4ANS ans=(PSOCK4ANS)(lpBuffer+1024);
req->vn = 4;
req->cd = 1;
req->port = ntohs((WORD)pInfo->srvport);
if((req->addr=inet_addr(pInfo->srvurl))==INADDR_NONE)
{
if(lphost=gethostbyname(pInfo->srvurl))
req->addr = ((LPIN_ADDR)lphost->h_addr)->s_addr;
}
send(_hSocket,lpBuffer,sizeof(SOCK4REQ),0);
if(!Recv(ans,sizeof(SOCK4ANS)))
{
if(lpMessage) sprintf(lpMessage,"代理服务器:%s:%d通讯失败",pInfo->srvurl,pInfo->srvport);
break;
}
if(ans->vn||ans->cd!=90)
{
if(lpMessage) sprintf(lpMessage,"代理服务器:%s:%d通讯失败",pInfo->srvurl,pInfo->srvport);
break;
}
}
return(TRUE);
}while(0);
UMySocket::Close();
return(FALSE);
}
//代理服务器连接
#pragma pack(push,1)
//sock4 req & ans
typedef struct tagSock4Req
{
char vn;
char cd;
WORD port;
DWORD addr;
char other[1];
}SOCK4REQ,*PSOCK4REQ;
typedef struct tagSock4Ans
{
char vn;
char cd;
}SOCK4ANS,*PSOCK4ANS;
//sock5 req & ans
typedef struct tagSock5Req
{
char ver;
char lmethods;
char methods[255];
}SOCK5REQ,*PSOCK5REQ;
typedef struct tagSock5Ans
{
char ver;
char method;
}SOCK5ANS,*PSOCK5ANS;
//sock5 check user
typedef struct tagAuthReq
{
char ver;
char ulen;
char user[255];
char plen;
char passwd[255];
}AUTHREQ,*PAUTHREQ;
typedef struct tagAuthAns
{
char ver;
char status;
}AUTHANS,*PAUTHANS;
typedef struct tagSock5ReqEx
{
char ver;
char cmd;
char rsv;
char atyp;
long addr;
WORD port;
}SOCK5REQEX,*PSOCK5REQEX;
typedef struct tagSock5AnsEx
{
char ver;
char rep;
char rsv;
char atyp;
char other[1];
}SOCK5ANSEX,*PSOCK5ANSEX;
#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状态图
--------------------------------------------------------------------------------------
https://leetcode.com/
tcp/ip详解 第1卷,UNP,APUE,TCP/IP协议族
《编程珠玑第2版·修订版》
《编程珠玑(续)(修订版)》
《编程之美——微软技术面试心得》
《剑指OFFER:名企面试官精讲典型编程题(第2版)》
《程序员代码面试指南:IT名企算法与数据结构题目最优解》
《程序员面试宝典(第5版)》