Linux 下socket编程总结,注意事项(1)

(一)基本socket api介绍—-TCP协议

socket
创建一个通信的端点。返回的是一个文件描述符fd:对于客户端来说,就是通过fd来与服务器来发起通信的,对于服务器来说,这个就是一个监听套接字。
bind
把socket创建的套接字绑定到指定的ip和port,因为socket系统调用就是告诉操作系统,我要一个通信啦,你要给我准备好来,这样,os就给我们创建了一个基本的数据结构,但是里面还没有填充数据,bind函数就是给数据结构填充数据的。
listen
把套接字变成监听套接字,对应的TCP状态为状态LISTEN。监听套接字只能用来监听用,也即只能做accept参数。
accept
套接字默认是阻塞的,当没有连接到来时,服务器一直阻塞在该调用上。当客户一旦调用connect函数,服务端也监听了套接字,那么内核给我们做了连个队列,一个是正在做三次握手的队列,一个是做好三次握手的队列。accept函数就是在做好三次握手队列中拿到一个连接,并且返回一个连接描述符connfd,这样我们就可以通过connfd描述符来与客户端通信了。
如果要做多并发服务器,那么只要像下面的模板一样
这个是进程的例子:

for()
{
    connfd = accept();
    pid = fork();
    if(pid == 0)
    {
        close(listenfd);//子进程不需要监听套接字
        while(1)
        {
            read(connfd ,...);
            dothing();
            write(connfd ,...);
        }
        close(connfd);//父进程不需要连接套接字
        exit(0);//子进程不参与下一次的fork。。。note
    }
    close(connfd);
}

这个是线程的例子:

while(1)
{
    connfd = accept();
    //方法一:给线程传递描述符,也即连接套接字,;
    //方法二:malloc一个,再在线程函数中free,方法一可行尽量使用第一种,这样就不需要使用malloc/free,这样的非线程安全函数
    pthread_create(&tid ,NULL , th_fun , (void*)connfd);

}
----------------------------------------
void * th_fun(void * arg)
{
    int connfd  = (int)arg;
    while(1)
        {
            read(connfd ,...);
            dothing();
            write(connfd ,...);
        }   
}

connect
调用该函数可以向客户端发起三部握手,默认是阻塞的,当正确返回时,就可以通过sockfd通信了。
close /shutdown
1、close的机制是描述符的引用技术。shutdown是直接发起关闭请求的。
2、close关闭的读写两端,shutdown可以有选择的关闭读写端
3、close,shutdown(r)都会激发tcp/ip的FIN分节的发送,对端读出就是0,说明关闭了。一定要处理的。
4、注意管道破裂,产生SIGPIPE信号。
setsocketopt
man 7 socket ip tcp可以找到都有那些选项
设置套接字选项。主要有SOL_SOCK,TCP_ , IP_,其中主要介绍几个用的上的。

//SO_ERROR
int optval;//其实就是一个开关
 getsockopt( sockfd,  SOL_SOCK,   SO_ERROR, &optval, sizeof(int);
 //对于设置非阻塞套接字的connect函数和accept函数,判断是否发生了错误
 -----------------------------------------
 //SO_LINGER
 //出现的原因是,当我们还有数据没有发完的时候,我们调用了close函数,如果设置了这个选项,那么就是告诉内核,你//等一等吧//(可以设置的等待时间),在关闭套接字。
   struct linger lin;
   lin.l_onoff = 1;//为1表示我们要打开这个选项, 0表示我们关闭了这个选项
   lin.l_linger = 5;//延迟的时间,现在是5秒
 setsockopt( sockfd,  SOL_SOCK,   SO_LINGER, &lin, sizeof(lin);
 ------------------------------------------------------------------------
 //SO_REUSEADDR
 //地址复用选项,出现的原因是,当服务器处于TIME_WAIT状态是,需要等待2MSL时间,一般都是超过一分钟,服务器一般都是绑定ip//和端口的,不设置的话,那么服务器就启动不起来。设置这个状态给最后的一个ack可以到达对端;允许重复的分节在网络中消失。
int optval = 1;//也是一个开关选项
 setsockopt(sockfd,SOL_SOCK , SO_REUSEADDR ,&optval ,sizeof(int));
 ---------------------------------------------------------------------------
//SO_RCVBUF and SO_SNDBUF
//SO_RCVLOWAT and SO_SNDLOWAT
这两个一般是不设置,因为close调用会发送一个

(二)使用selsect函数设置connect和accept函数的超时


connect_timeout()

int connect_timeout(int sockfd , struct sockaddr * addr , int len , long timeout)
{
    int ret = 0;
    ret = set_nonblock(sockfd , 0);//设置套接字的非阻塞模式
    if(ret == -1)
    {
        printf("fun connect_timeout() , set_nonblock() err\n");
        return ret;
    }
    ret = connect(sockfd ,addr , len );//直接连接一下,看看能不能立即返回,能最好,不能那么就使用下面的select超时优化
    if(ret != -1)
    {
        return ret;
    }
    if(ret == -1)
    {
        if(errno !=  EINPROGRESS)
        {
            //perror("fun connect_timeout() connect() err");
            return ret;
        }

    }
    ret = set_nonblock(sockfd , 1);
    if(ret == -1)
    {
        printf("fun connect_timeout() , set_nonblock() err\n");
        return ret;
    }
    fd_set rset ;
    FD_ZERO(&rset);
    FD_SET(sockfd , &rset);
     struct timeval tv;
     tv.tv_sec = timeout;
     tv.tv_usec = 0;
    ret = select(sockfd+1 , &rset , NULL ,NULL , &tv);
    if(ret == -1)
    {
        //perror("fun connect_timeout() select() err");
        return ret;
    }
    if(ret == 0)
    {
        ret = CLIENT_TIMEOUT;
        ret = -1;
        return ret;
    }
    return 0;   
}

accept_timeout

int accept_timeout(int sockfd , int timeout)
{
    int ret = set_nonblock() (sockfd , 0);
    if(ret == -1)
    {
        printf("fun accept_timeout() set_nonblock() err\n");
        return ret;
    }
    int fd = accept(sockfd , NULL , NULL);
    if(fd == -1)
    {
        // EAGAIN or EWOULDBLOCK
        if(errno != EAGAIN && errno != EWOULDBLOCK)
        {
            perror("fun accept_timeout() accept() err");
            return fd;
        }
    }
    if(fd >= 0)
    {
        return fd;
    }
    struct timeval tv;
    tv.tv_sec = timeout;
    tv.tv_nsec = 0;
    fd_set rset;
    FD_ZERO(&rset);
    FD_SET(sockfd ,&rset );
begin:  
    ret = select(sockfd +1 , &rset , NULL , NULL , &tv);
    if(ret == -1)
    {
        if(errno == EINTR)
        {
            goto begin;
        }
        perror("fun accept_timeout() select() err");
        return ret;
    }
    if(ret == 0)
    {
        errno = CLIENT_TIMEOUT;
        printf("fun accept_timeout() accept twe   select() timeout");
        return -1;
    }
    fd = accept(sockfd , NULL , NULL);
    if(fd == -1)
    {
        perror("fun accept_timeout() accept twe err ");
        return fd;
    }
    return fd;

}

(三)TCP/IP状态转化图

注意一下几点:
1、监听的时候内核给我们做了两个队列;
2、先执行close的一方先推进到time_wait状态;
3、如果对方不给close,那么本端就一直处于半连接状态,也即FIN_WAIT2状态。
4、为什么三次握手,四次断开?

心跳函数

讲的就是带外数据或者说紧急指针的概念。以及信号的处理。

对于发送带外数据:

send(fd , “a” , 1 , MSG_OOB);
在程序中写上这句话,有两个动作。动作1:给对端发送一个TCP首部带有URG标记的分节给对端, 分节中包含紧急数据的指针即位置,这个动作不管缓冲区,通告窗口的大小,直接就发送过去了。
动作2:把send的数据“a”放在发送队列中进行排队发送。

对于接受带外数据:

recv(fd , &recvbuf , 1 , MSG_OOB);
SO_OOBINLINE在线留存选项的设置与否。当没有设置时,带外数据是放在一个独立的单字节带外缓冲区中。
注意,如果你在接受的时候,发送紧急数据过快,你接受紧急数据过慢,可能会被覆盖。
当设置的时候,那么带外数据是放在套接字的接受缓冲区中。可以通过sockatmark()函数来确认是否含有带外数据。

使用select机制读取带外数据

带外数据只能读取一遍,读完之后,tcp会清空单字节的带外缓冲区,如果select继续检测异常条件,那么就会一直返回异常。

while(1)
{
    FD_SET(fd , &rset);
    if(justread == 0)
    {
        FD_SET(FD , &xset)
    }
    select(fd+1 , &rset , NULL , &xset , 0);
    if(FD_ISSET(fd , &rset))
    {
        read();
        ....
    }
    if(FD_SET(fd , &xset))
    {
        recv(...,MSG_OOB);
        justread = 1;
        FD_CLR(fd , &xset);
    }
    justread = 0;
}

客户/服务器心博函数

机制:当tcp收到带外数据时,内核给进程发送一个SIGURG信号,利用这一机制,就可以写出心博函数了。
客户端的信号处理程序

sig_alrm()
{
    if(++cnt>5)exit(0);
    send(MSG_OOB);
    alarm(1);
}
sig_urg()
{
    recv(MSG_OOB);
    cnt = 0;
}

服务器端

sig_urg()
{
    recv(MSG_OOB);
    send(MSG_OOB);
    cnt = 0;
}
sig_alrm()
{
    if(++cnt>5)exit(0);
    alarm(1);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值