socket编程

来源:互联网 发布:sql优化书籍推荐知乎 编辑:程序博客网 时间:2024/05/21 19:30

13. sockaddr_in清空方式

初始化可直接清空

sockaddr_in addr = {0};


1.TIME_WAITFIN_WAIT

问题:一方close(fd1),另一方没有close(fd2),导致调用方fd1处于TIME_WAIT和FIN_WAIT,socket不能使用。

解决方法:struct linger ling = {1,0} 跳过四步握手,直接强杀socket,socket直接CLOSED呈可用状态。


tcp连接的关闭经过FIN->ACK->FIN->ACK四步握手。

服务器先调用close(socket):


客户端先调用close(socket):


过程:

        1.A调用close(socket)发送FIN表示A的数据传输完成、没有其他数据要传输给B

        2.B发ACK对FIN进行应答

        3.A进入FIN_WAIT_2、B进入CLOSE_WAIT

        4.此时B仍旧可以发送数据给A(A的事儿处理完了不代表B的事儿也处理完了)

        5.B把其余数据发送给A完成传输

        6.B调用close(socket)发FIN

        7.A发ACK予以响应

        8.连接关闭


一个close()对应着一个FIN,一个FIN对应着一个close()调用。只有双方都使用close关闭socket后,连接才算关闭,以上就是以绅士的方式关闭TCP连接。但是不管是谁,率先调用close()的一方必回最终处于TIME_WAIT状态,在一段时间后方可变为CLOSED状态,此状态下的socket表可用。


在某些情况下,比如由服务器主动关闭连接的情况,如果client足够好调用了close(),则服务器端会有大量socket处于TIME_WAIT状态。如果client非善类,不曾调用close(),则服务器会有大量socket处于FIN_WAIT2状态下。这两种情况最终将会导致资源耗尽。


CentOS6.4 系统下为了解决这一问题,先后试着更改系统文件,close()前先调用shutdown()强制关闭,均不起作用。


最终解决办法如下:

server每接受一个新连接,即对生成的socket进行参数设置:

struct linger ling = {1,0};if(setsockopt(fd,SOL_SOCKET,SO_LINGER,(void*)&ling,sizeof(ling)) !=0){std::cout<<"set socket fail"<<std::endl;return;}
SO_LINGER显示需设置的选项,struct linger结构:

struct linger {int l_onoff;   //0即关闭此选项,1即打开此选项int l_linger;  //逗留时间};

有下列三种情况:

a、设置 l_onoff0,则该选项关闭,l_linger的值被忽略,等于内核缺省情况,close调用会立即返回给调用者,如果可能将会传输任何未发送的数据;

 

b、设置 l_onoff为非0l_linger0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT和FIN_WAIT2状态;

 

c、设置 l_onoff 为非0l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。


所以当设置ling = {1,0}就会扔掉缓冲区中的数据、跳过四步握手、直接进入CLOSED。


编写客户端程序,不断创建socket、connect服务器、收发数据,无限循环,不关闭socket。socket达到1024即出现:Too many open files, 原来一个程序不能打开过多socket。ulimit -a 可以查看到“open files      (-n) 1024”或者直接ulimit -n。

通常为1024,可以通过ulimit -n 65535修改为任意值。

ulimit -n num 只对当前窗口、当前shell有效。

显示网络连接:

netstat -antp | grep tcp |less

-a 显示所有socket  

-n 显示网络ip地址 

-t ·显示tcp协议连接状态

-p 显示指定协议信息


FIN2_WAIT和CLOSE_WAIT、TIME_WAIT都没有了。


服务器端send完数据后,马上close套接字,小试验暂时并无问题。可以想见若服务器端压力过大,数据进入缓冲区未及发送,此时的close()将意味着数据被抛弃。

2.服务器重启 Address already in use

当客户端保持着与服务器端的连接,这时服务器意外关闭,再开启服务器时会出现: Address already in use。

可以用netstat -anp | more 可以看到客户端还保持着与服务器的连接(还在使用服务器bind的端口)。这是由于client没有执行close,连接还会等待client的FIN包一段时间。

解决方法是使用setsockopt,使得socket可以被重用,是最常用的服务器编程要点。

具体的做法为是,在socket调用和bind 调用之间加上一段对socket的设置:

int flag = 1;if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*)&flag, sizeof(flag)) != 0){        std::cout<<"set addr reuse fail : "<<strerror(errno)<<std::endl;        return -1;}

3.把socket设置成非阻塞

[cpp] view plaincopy
  1. int flags;  
  2.    
  3. if((flags = fcntl(fd, F_GETFL)) < 0) //获取当前的flags标志  
  4.    
  5.   err_sys(“F_GETFL error!”);  
  6.    
  7. flags |= O_NONBLOCK; //修改非阻塞标志位  
  8.    
  9. if(fcntl(fd, F_SETFL, flags) < 0)  
  10.    
  11.   err_sys(“F_SETFL error!”);  


4.close(fd);关闭socket后,连接断开,socket被销毁

Aconnect到B,B发生异常,close(fd2)断开连接,A端socket收到连接异常。A要进行重连必须按如下顺序操作:

a.close(fd1) 关闭自身socket

b.socket() 创建新socket

c.connect() 重连


5.创建socket套接字:

/* 函数原型 */

int socket(intdomain,inttype,intprotocol);

/* 头文件 */

#include <sys/types.h>

#include <sys/socket.h>



6.设置socket

#include <sys/types.h>#include <sys/socket.h>struct sockaddr_in sin;memset(&sin,0,sizeof(sin));sin.sin_family = AF_INET;sin.sin_addr.s_addr = htonl(INADDR_ANY);sin.sin_port = htons(8080);

htonl(INADDR_ANY)等价于inet_addr(“0.0.0.0”),表示本机所有地址,在本机网卡有多个或者替换网卡时就不用特意修改了。

而inet_addr(“127.0.0.1”)多用于测试使用,telnet 192.168.15.12 8080就会出现“connection refused”。


7.inet_addr头函数#include<arpa/inet.h>


8.从sockaddr中取ip和port方法

struct sockaddr_in sin;

memcpy(&sin,addr,sizeof(sin));


sockaddr和sockaddr_in可以相互类型转换,故简单方法如下:

struct sockaddr_in *sin = (struct sockaddr_in*)addr;


然后:

std::cout<<"ip : "<<inet_ntoa(sin->sin_addr)<<std::endl;
std::cout<<"port : "<<ntohs(sin->sin_port)<<std::endl;


9.设置socket缓冲区

int bufLen = 100 * 1024;  int ret = setsockopt(Socket,SOL_SOCKET,SO_RCVBUF,(char *)&bufLen,sizeof(bufLen)); if(ret != 0){std::cout<<"Set Socket Buffer Fail: "<<GetLastError()<<std::endl;return -1;}

10. recvfrom : GetLastError() = 10040

recvfrom成功返回0;错误返回-1,GetLastError()返回错误码。

若错误码为10040,很可能是用于接收数据的自定义缓冲区小了的缘故。


11.socket设置非阻塞

设置为非阻塞:

        unsigned long ul = 1;

        int ret = ioctlsocket(s, FIONBIO, &ul);

设置为阻塞:

        unsigned long ul = 0;

        int ret = ioctlsocket(s, FIONBIO, &ul);

返回值:

        if(SOCKET_ERROR == ret)

        {

                std::cout<<"ioctlsocket Error : "<<GetLastError()<<std::endl;

                closesocket(s);

                s = INVALID_SOCKET_VALUE;

                return false;

        }


12.socket编程实例

server

#include <iostream>#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>  //htons() htonl()#include <errno.h>      //errno#include <string.h>     //strerror()#include <unistd.h>     //sleep()int main(){        struct sockaddr_in sin;        socklen_t slen;        int fd, cfd;        int data_len;        char buffer[30] = {0};        fd = socket(AF_INET, SOCK_STREAM, 0);        if(fd < 0){                std::cout<<"create socket fail"<<std::endl;                return -1;        }        struct linger ling = {1, 0};        /*if(setsockopt(fd, SOL_SOCKET, SO_LINGER, (void*)&ling, sizeof(ling)) != 0){                std::cout<<"set linger fail : "<<strerror(errno)<<std::endl;                return -1;        }*/        int flag = 1;        if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*)&flag, sizeof(flag)) != 0){                std::cout<<"set addr reuse fail : "<<strerror(errno)<<std::endl;                return -1;        }        sin.sin_family = AF_INET;        sin.sin_port = htons(6666);        sin.sin_addr.s_addr = htonl(INADDR_ANY);        if(bind(fd, (struct sockaddr*)&sin, sizeof(sin)) != 0){                std::cout<<"bind fail : "<<strerror(errno)<<std::endl;                return -1;        }        if(listen(fd, 50) != 0){                std::cout<<"listen fail"<<std::endl;                return -1;        }        slen = sizeof(sin);        cfd = accept(fd, (sockaddr*)&sin, &slen);        if(cfd < 0){                std::cout<<"accept fail"<<std::endl;                return -1;        }        data_len = recv(cfd, buffer, sizeof(buffer), 0);        if(data_len <= 0){                if(data_len == 0)                        std::cout<<"client close socket"<<std::endl;                else                        std::cout<<"some error fail"<<std::endl;                return -1;        }        buffer[data_len] = '\0';        std::cout<<buffer<<std::endl;         return 0;}

client

int main(){        struct sockaddr_in sin;        memset(&sin,0,sizeof(sin));        sin.sin_family = AF_INET;        sin.sin_addr.s_addr = inet_addr("127.0.0.1");        sin.sin_port = htons(8080);        int fd = socket(AF_INET, SOCK_STREAM, 0);        if(connect(fd,(struct sockaddr*)&sin,sizeof(sin))<0){                std::cout<<"connect fail"<<std::endl;                return 1;        }        char buf[50] = {0};        sprintf(buf,"saldkfjasad");        if(send(fd,buf,sizeof(buf),0)<0){               std::cout<<"send fail"<<std::endl;                return 1;        }        int len = recv(fd,buf,sizeof(buf),0);        buf[len] = '\0';        std::cout<<buf<<std::endl;        if(close(fd) != 0)                std::cout<<"close socket fail"<<std::endl;}



0 0
原创粉丝点击