网络编程

网络编程
套接字编程
  • 套接字地址结构:

    • struct sockaddr定义了一种通用的套接字地址

    • 
      #include<linux/socket.h>
      
      struct sockaddr
      {
        unsigned short sa_family;//地址类型,AF_xxx
        char sa_data[14];//14字节的协议地址
      };
      • 参数:

        • sa_family:套接字的协议族类型。对应TCP/IP协议该值为AF_INET
        • sa_data:存储具体的协议地址。
      • 说明:一般在编程中并不对该结构体进行操作,而是使用另一个与他等价的数据结构:sockaddr_in

    • sockaddr_in:TCP/IP协议族的地址格式(每种协议族都有自己的协议地址格式):

    • 
      #include<netinet/in.h>
      
      struct sockaddr_in
      {
        unsigned short sin_family;//地址类型
        unsigned short int sin_port;//端口号
        struct in_addr sin_addr;//IP地址
        unsigned char sin_zero[8];//填充字节,一般赋值0
      };
      sin_family:对于TCP/IP协议进行的网络编程,该值只能是AF_INET
      
      struct in_addr
      {
        unsigned long s_addr;
      }
      • 说明:sockaddr的长度为16字节,sockaddr_in的长度也为16字节。通常在编写基于TCP/IP协议的网络程序时,使用sockaddr_in来设置地址,然后通过前置转换转换成sockaddr类型
    • 设置地址信息的实例代码:

    struct sockaddr_in sock;
    sock.sin_family=AF_INET;
    sock.sin_port=htons(80);//设置端口号80
    sock.sin_addr.s_addr=inet_addr("202.205.3.195");//设置地址
    memset(sock.sin_zero,0,sizeof(sock.sin_zero));//将数组清0
创建套接字
  • socket:创建套接字

    
    #include<sys/types.h>
    
    
    #include<sys/socket.h>
    
    int socket(int domain,int type,int protocol);
    • 参数:

    • domain:用于指定创建套接字所使用的协议族,他们在头文件linux/socket.h中定义,常用的协议族如下:

    domain含义
    AF_UNIX创建只在本机内进行通信的套接字
    AF_INET使用IPv4 TCP/IP协议
    AF_INEF6使用IPv6 TCP/IP协议
    • type;指定套接字的类型:
    type含义
    SOCK_STREAM创建TCP流套接字
    SOCK_DGRAM创建UDP数据报套接字
    SOCK_RAW创建原始套接字
    • protocol:通常设置为0,表示通过参数domain指定的协议族和参数type指定的套接字类型来确定使用的协议。当创建原始套接字时。系统无法唯一的确定协议,此时就要用该参数指定所使用的协议

    • 执行情况返回值
      成功一个新创建的套接字
      失败-1,错误代码存errno
    • 实例:

    • 创建一个TCP套接字:

    int sock_fd;
    sock_fd=socket(AF_INET,SOCK_STREAM,0);
    if(sock_fd<0)
    {
        perror("socket");
        exit(1);
    }
    • 创建UDP协议的套接字
    int sock_fd;
    sock_fd=socket(AF_INET,SOCK_DGRAM,0);
    if(sock_fd<0)
    {
        perror("socket");
        exit(1);
    }
建立连接
  • connect:用来在一个指定的套接字上创建一个链接

    
    #include<sys/types.h>
    
    
    #include<sys/socket.h>
    
    int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);
    • 参数:

    • sockfd:由函数socket创建的套接字。

      • 如果套接字的类型是SOCK_STREAM,则connect函数用于向服务器发送连接请求,服务器的IP地址和端口号由参数serv_addr指定。
      • 若果套接字的类型是SOCK_DGRAM,则connect函数并不建立真正的连接,他只是告诉内核与该套接字通信的目的地址(由第二个参数指定),只有该目的地址发来的数据才会被socket接受,对他来说调用connect的好处是不必在每次发送和接收数据是都指定目的地址。
      • 说明:通常一个面向连接的套接字(TCP套接字)只能调用一次connect函数,对于无连接的套接字(UDP套接字)则可以多次调用connect函数以改变与目的地址的绑定。将参数serv_addr中的sa_family设置为AF_UNSPEC可以取消绑定。
    • serv_addr:一个地址结构
    • addrlen:是参数serv_addr的长度

    • 返回

执行情况返回值
成功返回0
失败返回-1,错误代码在errno

用法:

  struct sockaddr_in serv_addr;
  memset(&serv_addr,0,sizeof(struct sockaddr_in));//将serv_addr的各个字段请0
  serv_addr.sin_family=AF_INET;
  serv_addr.sin_port=htons(80);
  if(inet_aton("172.17.242.131",&serv_addr.sin_addr)<0)
  {
      perror("inet_aton");
      exit(1);
  }
  //使用sock_fd套接字连接到serv_addr指定,假设sock_fd已经定义
  if(connect(sock_fd,(struct sockaddr*)&serv_addr,sizeof(struct sockaddr_in))<0)
  {
      perror("connect");
      exit(1);
  }
绑定套接字
  • bind:将一个套接字和某个端口绑定在一起(一般只有服务器端的程序调用)

    
    #include<sys/types.h>
    
    
    #include<sys/socket.h>
    
    int bind(int sockfd,struct sockaddr *my_addr,socklen_t addrlen);
    • 说明:socket函数只是创建了一个套接字,这个套接字将工作在哪个端口上,程序并没有指定。在客户机/服务器模型中,服务器端的IP地址和端口号是固定的,因此在服务器端的程序中,使用bind函数

    • 参数:

    • my_addr:制定了sockfd将绑定到的本地地址,可以将参数my_addr的sin_addr设置为INADDR_ANY而不是确定的一个IP地址,这样套接字就可以绑定任何网络接口了

      • 对于只有一个IP地址的计算机,INADDR_ANY对应的就是他的IP地址。
      • 对于多宿主主机(拥有多块网卡),INADDR_ANY表示本服务器程序将处理来自所有网络接口上相应端口的连接请求
    • 执行情况返回值
      成功返回0
      失败返回-1,,错误代码在errno
    • 用法:

    struct sockaddr_in serv_addr;
    memset(&serv_addr,0,sizeof(struct sockaddr_in));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_port=htons(80);
    serv_addr.sin_addr.s_addr=hton1(INADDR_ANY);
    if(bind(sock_fd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr_in))<0)
    {
      perror("bing");
      exit(1);
    }
在套接字上监听
  • listen:把套接字转化为被动监听

    
    #include<sys/socket.h>
    
    int listen(int a,int backlog);

    说明:

    • 由socket创建的套接字是主动套接字,这种套接字可以用来主动请求连接到某个服务器(通过connect)但是作为服务器端的程序,通常在某个端口上监听来自客户端的连接请求。在服务器端,一般是先调用socket创建一个主动套接字,然后调用bind将该套接字绑定到某个端口上,再用listen将该套接字转化为监听套接字,等待来自客户端的连接请求。
    • 一般多个客户端连接到一个服务器上,服务器向这些客户端提供某种服务。服务器端设置一个连接队列,记录已经建立的连接。
    • listen只是将套接字设置为倾听模式以等待连接请求,它并不能接受连接请求,真正接受连接请求的是accept

    • 参数:

    • s:创建的套接字

    • backlog:制定了该连接队列的最大长度。如果连接队列已经达到最大,之后的连接请求将被服务器拒绝

    • 执行情况返回值
      成功返回0
      失败返回-1,错误代码在errno
    • 实例:

    
    #define LISTEN_NUM 12//定义连接请求队列长度
    
    if(listen(sock_fd,LISTEN_NM)<0)
    {
      perror("listen");
      exit(1);
    }
接受连接
  • accept:接受一个连接请求

    
    #include<sys/types.h>
    
    
    #include<sys/socket.h>
    
    int accept(int s,struct sockaddr *addr,socklen_t *addrlen);
    • 参数:

    • s:socket创建,经bind绑定到本地端口,通过listen转化而来的监听套接字

    • addr:保存发起连接请求的主机的地址和端口
    • addrlen:addr所指向的结构体的大小

    • 执行情况返回值
      成功新的代表客户端的套接字
      失败返回-1,错误代码在errno
    • 说明:

    • 只能对面向连接的套接字使用accept函数,执行成功则创建一个新的套接字,并为这个新的分配一个套接字描述符,并返回这个描述符。

    • 如果s指定的套接字被设置为阻塞方式(Linux下的默认方式),且连接请求对列为空,则accept江北阻塞直到有连接请求到达为止。
    • 若果s指定的套接字设置为费阻塞方式(fcntl),则如果队列为空,accept立即返回-1,errno设置为EADIAN。

    • (阻塞方式下的)实例:

    int client_fd;
    int client_len;
    struct sockaddr_in client_addr;
    client_len=sizeof(struct sockaddr_in);
    client_fd=accept(sock_fd,(struct sockaddr *)&client_addr,&client_len);
    if(conn_fd<0)
    {
      perror("accept");
      exit(1);
    }
TCP套接字的数据传输
  • 发送数据:send

    
    #include<sys/types.h>
    
    
    #include<sys/socket.h>
    
    ssize_t send(int s,const void *msg,size_t len,int flags);
    • 说明:

    • send只能对处于连接状态的套接字使用

    • 参数:

    • s:已经建立好连接的套接字描述符,即函数的返回值。

    • msg:指向存放待发送数据的缓冲区
    • len:待发送数据的长度
    • flags:控制选项,设置为0或下面的选项
    flags含义
    MSG_OOB在指定的套接字上发送带外数据,该类型的套接字必须支持带外数据(如SOCK_STREAM)
    MSG_DONTROUTE通过最直接的路径发送数据,而忽略下层协议的库有设置
    • 执行情况返回
      发送的数据太长不能发送出现错误,errno设置为EMSGSIZE
      发送的数据长度大于该套接字的缓冲区剩余空间大小send会阻塞 如果该套接字设置为非阻塞,立即返回-1,errno设置为EAGAIN
      执行成功返回实际发送的字节数
    • 执行成功只是说明数据写入套接字的缓冲区,并不表示数据已经成功通过网络发送到目的地

    • 套接字为阻塞下的实例:

    
    #define BUFFERSIZE 1500
    
    char send_buf[BUFFERSIZE];
    ...
    if(send(conn_fd,send_buf,len,0)<0)//len为待发送的字节数
    {
      perror("send");
      exit(1);
    }
  • 接收数据:recv

    
    #include<sys/types.h>
    
    
    #include<sys/socket.h>
    
    ssize_t recv(int s,void *buf,size_t len,int flags);
    • 函数recv从参数s所指定的套接字描述符(必须是面向连接的)上接收数据并保存到参数buf所制定的缓冲区,参数len为缓冲区的长度。

    • flags:控制选项,一般为0或下面的:

    flags含义
    MSG_OOB请求接受带外数据
    MSG_PEEK只查看数据而不读出
    MSG_WAITALL只在接收缓冲区满时才返回
    • 执行情况说明
      如果一个数据包太长以至于缓冲不能完全放下剩余的数据将可能被丢弃(根据接收数据的套接字类型而定)
      指定的套接字上无数据到达recv将被阻塞
      指定的套接字上无数据到达且指定的套接字被设置为非阻塞方式立即返回-1,errno设置为EAGAIN
      recv接收到数据就返回并不会等待接收到参数len指定的长度的数据才返回
      执行成功返回接收到的数据字节数
      执行失败返回-1,错误代码errno中
    • 套接字为阻塞方式下该函数的实例:

    char recv_buf[BUFFERSIZE];
    ...
    if(recv(conn_fd,recv_buf,sizeof(recv_buf),0)<0)
    {
      perror("recv");
      exit(1);
    }
UDP套接字的数据传输
  • 发送数据:sendto

    
    #include<sys/types.h>
    
    
    #include<sys/socket.h>
    
    ssize_t sendto(int s,const void *msg,size_t len,int flags,const struct sockaddr *to,socklen_t tolen);
    • 说明:sendto不需要套接字处于连接状态。因为是无连接的套接字,在使用sento时要指定数据的目的地址。

    • 参数:

    • msg:指向待发送数据的缓存区

    • len:指定了待发送数据的长度
    • flags:控制选项与send中的一样
    • to:指定目的地址
    • tolen:目的地址的长度

    • 执行情况含义
      成功返回实际发送数据的字节数
      失败返回-1,错误代码errno中
    • 实例;

    char send_buf[BUFFERSIZE];
    struct sockaddr_in dest_addr;
    //设置目的地址
    memset(&dest_addr,0,sizeof(struct sockaddr_in));
    dest_addr.sin_family=AF_INET;
    dest_addr.sin_port=htons(DEST_PORT);
    if(inet_aton("172.17.242.131",&dest_addr.sin_addr)<0)
    {
      perror("inet_aton");
      exit(1);
    }
    if(sendto(sock_fd,send_buf,len,0,(struct sockaddr *)&dest_addr,sizeof(struct sockaddr_in))<0)
    {
      perror("sendto");
      exit(1);
    }
  • 接收数据:recvfrom

    
    #include<sys/types.h>
    
    
    #include<sys/socket.h>
    
    ssize_t recvfrom(int s,void *buf,size_t len,int flags,struct sockaddr *from,socklen_t *fromlen);
    • 说明:没有限制,也可以用于无连接的套接字

    • 参数:

    • buf:指向接受缓存区

    • len:制定了缓冲区的大小
    • flags:控制选项与recv一致
    • from:如果为非空,且该套接字不是面向连接的,则函数返回时,from中将保存数据的源地址
    • fromlen:调用recvfrom前为参数from的长度,调用后保存from的实际大小

    • 执行情况含义
      成功返回实际接收到数据的字节数
      失败返回-1,错误代码在errno
    • 套接字为阻塞方式下该函数的实例:

      char recv_buf[BUFFERSIZE];
      struct sockaddr_in src_addr;
      int src_len;
      src_len=sizeof(struct sockaddr_in);
      if(recvfrom(sock_fd,recv_buf,sizeof(recv_buf),0,(struct sockaddr *)&src_addr,&src_len)<0)
      {
        perror("again_recvfrom");
        exit(1);
      }
关闭套接字
  • close:

    • 作用:关闭一个套接字描述符

      
      #include<unistd.h>
      
      int close(int fd);//fd:套接字描述符
    • 执行情况返回值
      成功返回0
      失败返回-1错误代码在errno
  • shutdown

    • 作用:关闭一个套接字描述符
    
    #include<sys/socket.h>
    
    int shutdown(int s,int how);

    比close功能更强大,它允许对套接字进行单项关闭或全部禁止

    • 参数:

    • s:待关闭的套接字描述符

    • howto:关闭的方式

      howto含义
      SHUT_RD将连接上的读通道关闭,此后进程将不能接受任何数据,接收缓冲区中还没有被读的数据也被丢失,但仍然可以在该套接字上发送数据
      SHUT_WR将连接上的写通道关闭,此后进程在不能发送任何数据,发送缓冲区中还没有被发送的数据也将被丢失,但仍然可以写
      SHUT_RDWR读写通道都关闭
    • 执行情况返回值
      成功返回0
      失败返回-1,错误代码errno中
主要系统调用函数
字节顺序转换函数
  • 大端模式:(网络字节顺序)

    • 高字节放在低地址处,低字节在高地址处

    • eg: 0x04030201

    0x10000x04
    0x10010x03
    0x10020x02
    0x10030x01
  • 小端模式:

    • 低字节在低地址处,高字节在高地址处:
    • eg:0x04030201
0x10000x01
0x10010x02
0x10020x03
0x10030x04

* 在网络上传输数据时,由于数据传输的两端可能对应不同的硬件平台,采用的存储字节顺序也可能不一致,因此TCP/IP协议规定在网络上必须采用网络字节顺序(大端模式)。通过对大小端模式存储原理的分析,对于char型数据,由于其只占一个字节,所以不存在这个问题,这也是我们一般把数据缓存区定义为char型的原因之一。对IP地址,端口号等非char型数据,必须在数据发送到网络上之前将其转换成大端模式,在接收到数据后再将其转换成符合接收端主机的存储模式

  • 字节转换由四种:

    
    #include<netinet/in.h>
    
    uint32_t htonl(uint32_t hostlong);
    uint16_t htons(uint16_t hostshort);
    uint32_t ntohl(uint32_t netlong);
    uint16_t ntohs(uint16_t netshort);
    • 含义:
    • htonl:host to network long:将主机unsigned int型转换成网络字节顺序
    • htons:host to network short:将主机unsigned short型数据转换成网络字节顺序
inet系列函数
  • 前提:通常我们习惯于使用字符串形式的网络地址(如”172.17.242.131“),然而在网络上进行数据通信时,需要使用的是二进制形式且为网络字节顺序的IP地址。eg:172.17.242.131对应的二进制顺序为0x83f211ac
  • 头文件;
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
  • 函数:

    • inet_aton:

    • int inet_aton(const char *cp,struct in_addr *inp);
    • 将cp所指向的字符串形式的IP地址转换为二进制的网络字节顺序的IP地址,转换后的结构存于参数inp所指向的空间

    • 执行情况返回值
      执行成功返回非0
      参数无效返回0
    • inet_aton:

    • in_addr_t inet_addr(const char *cp);
    • 将cp所指向的字符串形式的网络地址转换为二进制的网络字节顺序的IP地址。

    • 执行情况含义
      成功将转换后的结果返回
      参数无效INADDR_NONE(一般该值为-1)
    • 说明:对有效地址”255.255.255.255“它也返回-1,使得用户可能将这个当成无效的非法地址。

    • inet_network:

    • in_addr_t inet_network(const char *cp);
    • 将参数cp所指向的字符串形式的网络地址转换为主机字节顺序形式的二进制IP地址

    • 执行情况含义
      执行成功返回转换后的结果
      参数无效返回-1
    • inet_ntoa

    • char *inet_ntoa(struct in_addr in);
    • 将值为in的网络字节顺序形式的二进制IP地址转换成以”.“分割的字符串形式。

    • 执行情况含义
      执行成功返回结果字符串的指针
      参数无效返回NULL
    • inet_makeaddr:

    • struct in_addr inet_makeaddr(int net,int host);
    • 将网络号为参数net,主机号为参数host的两个地址组合成一个网络地址。

    • eg:net取0xac11(172.17.0.0 主机字节顺序形式),host取0xf283(0.0.242.131,主机字节顺序),则组合后的地址为:172.17.242.131,并表示为网络字节顺序形式0x83f211ac.

    • inet_inaof

    • in_addr_t inet_inaof(struct in_addr in);
    • 该函数从in中提取出主机地址

    • 执行情况返回值
      执行成功返回主机字节顺序形式的主机地址

      eg:172.17.242.131,属于B类地址,则主机地址为0.0.242.131 按主机字节顺序形式输出为:0xf283

    • inet_netof

    • in_addr_t inet_netof(struct in_addr in);
    • 该函数从in中提取出来网络地址

    • 执行情况返回值
      执行成功返回主机字节顺序形式的网络地址
  • 代码实例:

#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main(void)
{
    char buffer[32];
    int ret=0;
    int host=0;
    int network=0;
    unsigned int address=0;
    struct in_addr in;
    in.s_addr=0;
    /*输入一个以"."分割的字符串形式的IP地址*/
    printf("please input your ip address:");
    fgets(buffer,31,stdin);
    buffer[31]='\0';
    /*实例使用inet_aton()函数*/
    if((ret=inet_aton(buffer,&in))==0)
        printf("inet_aton:\tinvalid address\n");
    else
        printf("inet_aton:\t0x%x\n",in.s_addr);
    /*实例使用inet_addr()函数*/
    if((address=inet_addr(buffer))==INADDR_NONE)
        printf("inet_addr:\tinvalid address\n");
    else
        printf("inet_addr:\t0x%x\n",address);
    /*实例使用inet_network函数*/
    if((address=inet_network(buffer))==-1)
        printf("inet_network:\tinvalid address\n");
    else
        printf("inet_work:\t0x%x\n",address);
    /*实例使用inet_ntoa函数*/
    if(inet_ntoa(in)==NULL)
        printf("inet_ntoa:\tinvalid address\n");
    else
        printf("inet_ntoa:\t%s\n",inet_ntoa(in));
    /*实例使用inet_lnaof()和inet_netof()函数*/
    host=inet_lnaof(in);
    network=inet_netof(in);
    printf("inet_inaof:\t0x%x\n",host);
    printf("inet_netof:\t0x%x\n",network);
    in=inet_makeaddr(network,host);
    printf("inet_makeaddr:0x%x\n",in.s_addr);
    return 0;
}

![2018-08-09 16-13-12 的屏幕截图](/home/lala/图片/2018-08-09 16-13-12 的屏幕截图.png)

getsockopt()和setsockopt()
  • 作用:

    • getsockopt:获取套接字的属性
    • setsockopt:设置套接字的属性
  • 
    #include<sys/types.h>
    
    
    #include<sys/socket.h>
    
    int getsockopt(int s,int level,int optname,void *optval,socklen_t *optlen);
    int setsockopt(int s,int level,int optname,const void *optval,socklen_t optlen);
  • 参数:

    • s:套接字
    • level:进行套接字选项操作的层次
level含义
SOL_SOCKET通用套接字(用它来进行与特定协议不相关的操作)
IPPROTO_IPIP层套接字
IPPROTO_TCPTCP层套接字

* optname:套接字选项的名称

getsockopt参数含义
optval存放获得的套接字选项
optlen调前:optval指向的空间大小d调后:optval所保存结果的实际大小
setsockopt参数含义
optval待设置的套接字选项的值
optlen该选项的长度

* SOL_SOCKET选项:

  • SO_KEEPALIVE

    • 如果没有设置这个选项,那么即使TCP链接已经很长时间没有数据传输时,系统也不会检测这个连接是否有效对于服务器进程,如果某一客户端非正常断开连接,这服务器进化出呢个将一直被阻塞等待。因此,服务器端程序需要这个选项,如果某个客户端一段时间没有反应则关闭该连接
  • SO_RCVLOWAT 和 SO_SNDLOWAT

    • 每个套接字还有一个接收低水位标记和一个发送低水位标记。他们由select函数使用,这两个套接字选项允许我们修改这两个低水位标记。
    • 接收低水位标记是让select返回“可读”时,套接字接收缓冲区中所需的数据量。对于TCP,UDP和SCTP套接字,其默认值为1发送低水位标记是让select返回“可写”时套接字发送缓冲区中所需的可用空间对于TCP套接字,其默认值通常为2048。UDP也使用发送低水位标记,然而由于UDP套接字的发送缓冲区中可用空间的字节数从不改变(意味UDP并不为由应用进程传递给它的数据报保留副本),只要一个UDP套接字的发送缓冲区大小大于该套接字的低水位标记,该UDP套接字就总是可写。我们记得UDP并没有发送缓冲区,而只有发送缓冲区大小这个属性。
  • SO_RCVTIMEO 和 SO_SNDTIMEO

    • 这两个选项允许我们给套接字的接收和发送设置一个超时值。注意,访问他们的getsockopt和setsockopt函数的参数是指向timeval结构的指针,与select所用参数相同。这可让我们用秒数和微妙数来规定超时。我们通过设置其值为0s和0μs来禁止超时。默认情况下着两个超时都是禁止的
    • 接收超时影响5个输入函数:read,readv,recv,recvfrom和recvmsg。
    • 发送超时影响5个输出函数:write,writev,send,sendto和sendmsg。
    • 具体时间有下面这个结构指定:
    struct timeval
    {
        long tv_sec;//秒数
        long tv_usec;//微秒数
    };//超过时间为这两个之和
    • 如果超时:则认为接受或者发送数据失败
  • SO_BINDTODEVICE

    • 将套接字绑定到特定的网络接口如”eth0“,以后只有该网络接口上的数据才会被套接字处理。如果将该选项值设置为空字符串或选项长度设为0将取消绑定。
  • SO_DEBUG

    • 只能对TCP套接字使用,设置了之后系统保存TCP发送和接受的所有数据的相关信息
  • SO_REUSEADDR

    • 如果一个socket绑定了一个端口,该socket正常关闭或程序异常退出后的一段时间内,该端口仍然维持原来的状态,其他程序无法绑定该端口,如果设置了该选项即可避免这个问题。
    int option_value=1;
    int length=sizeof(option_value);
    setsockopt(sock_fd,SOL_SOCKET,SO_REUSERDDR,&option_value,length);
  • SO_TYPE

    • 用于获取套接的类型(只能被函数getsockopt获取套接字的类型,不能用setsockopt修改套接字的类型)
  • SO_ACCEPTCONN

    • 用来检测套接字是否处于监听状态(只能用getsockopt来获取监听状态)
      • 0:非监听状态
      • 1:正在监听
  • SO_DONTROUTE:

    • 这只该选项表示:在发送IP数据包时不能使用路由表来寻找路由
  • SO_BROADCAST:

    • 用来决定套接字是否能够在网络上广播数据。实际应用中要在网络上广播数据必须硬件支持广播(如以太网支持广播)并且使用的是SOCK_DGRAM套接字。系统默认不支持广播,如果希望SOCK_DGRAM支持广播;

      int option_value=1;
      setsockopt(sock_id,SOL_SOCKET,SO_BROADCAST,&option_value,sizeof(int));
  • SO_SNDBUF和SO_RCVBUF

    • 用于设置套接字的发送和接收缓冲区的大小
      • 对于TCP类型的套接字,缓冲区太小会影响TCP的流量控制;
      • 对于UDP类型的套接字,如果套接字的数据缓冲区蛮厚则及后续数据将被丢弃
  • SO_ERROR

    • 获取套接字的内部错误变量errno,当套接字上发生了异步错误时,系统将设置套接字的so_error/
    • 异步错误:错误的发生和错误被发现的时间不一致,通常在目的主机非正常关闭时发生这种错误。
    • 只能是被函数getsockopt用来获取so_error,调用完后so_error的值将自动被重新初始化.
    多路复用select

    在客户端服务器模型中,服务器端需要同时处理多个用户端的链接要求

    实现多路复用最简单的方式是:非阻塞方式套接字

    服务器不断查询各个套接字的状态,如果有数据到达则读出数据,如果没有则查看下一套接字

    另一种方法:向系统登录希望坚实的套接字,然后阻塞

    当套接字有事件发生时(有数据到达等),系统通知服务器进程告知哪个套接字上发生了什么事件,服务器进程查询对应套接字并进行处理。套接字上没有事件发生时,服务器进程不会去查询套接字的状态(select)

  • 
    #include<sys/types.h>
    
    
    #include<sys/time.h>
    
    
    #include<sys/select.h>
    
    
    #include<unistd.h>
    
    int select(int n,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
  • 参数;

    • n:需要监视文件描述符数
    • readfds:制定需要监视的可读文件描述符集合,当这个集合中的一个描述符上有数据到达时,系统将通知调用select函数的程序
    • writefds:指定需要监视的可写文件描述符集合,当这个集合中的某个描述符可以发送数据时,程序将受到通知。
    • exceptfds:指定需要监视的异常文件描述符集合,当集合中的一个有异常时,程序将受到通知
    • timeout:制定了阻塞的时间,如果在这段时间内坚实的文件描述符上都没有事件发生,则函数select()返回0
    struct timeval
    {
        long tv_sec;//seconds
        long tv_usec;//微秒
    }
    • timeout含义
      NULL则函数select将一直被阻塞,直到某个文件描述符上发生了事件
      0此时相当于非阻塞方式,select查询完文件描述符集合的状态后立即返回
      某一时间值在这个时间内如果没有事件发生,函数将返回; 有时间发生,程序接到通知。
      • 注:文件描述符:普通文件描述或套接字描述符
      • 系统为文件描述符提供了一系列宏:
      FD_CLR(int fd,fd_set *set);//将文件描述符fd从文件描述符集合set中删除
      FD_ISSET(int fd,fd_set *set);//测试fd是否在set中
      FD_SET(int fd,fd_set *set);//在文件描述符集合中增加文件描述符fd
      FD_ZERO(fd_set *set);//将文件描述符集合set清空
      • 如果select设定的要监视的文件描述符集合中由描述符发生了事件,则select将返回发生事件的文件描述符的个数
      • select实例:
      
      #include<sys/time.h>
      
      
      #include<stdlib.h>
      
      
      #include<stdio.h>
      
      
      #include<sys/types.h>
      
      
      #include<unistd.h>
      
      
      #include<time.h>
      
      void display_time(const char *string)
      {
        int seconds;
        seconds=time((time_t*)NULL);
        printf("%s,%d\n",string,seconds);
      }
      int main(void)
      {
        fd_set readfds;
        struct timeval timeout;
        int ret;
        /*监视文件描述符0是否有数据输入,文件描述符0表示标准输入,即键盘输入*/
        FD_ZERO(&readfds);//开始使用一个描述符集合前一般要将其清空
        FD_SET(0,&readfds);
        /*设置阻塞时间为10秒*/
        timeout.tv_sec=10;
        timeout.tv_usec=0;
        while(1)
        {
            display_time("berore select");
            ret=select(1,&readfds,NULL,NULL,&timeout);
            display_time("after select");
            switch(ret)
            {
                case 0:
                    {
                        printf("No data in ten seconds\n");
                        exit(0);
                        break;
                    }
                case -1:
                    {
                        perror("select");
                        exit(1);
                        break;
                    }
                default:
                    {
                        getchar();//将数据读入,否则标准输入上将一直:为读就绪
                        printf("Data is available now\n");
                    }
            }
        }
      }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一个简单的socket网络编程例子: 服务器代码: #include #include #include #include #pragma comment(lib,"ws2_32.lib") //这句话的意思是加载ws2_32.lib这个静态库 #define NETWORK_EVENT WM_USER+100 //如果你用mfc做开发,你可以点击菜单project-〉setting-〉link-〉object/library中添加这个静态库。 //如果你用c语言,你需要通过#pragma comment(命令来连接静态库 int main(int argc, char* argv[]){ HANDLE hThread = NULL; //判断是否输入了端口号 if(argc!=3){ printf("Usage: %sPortNumber\n",argv[1]); exit(-1); } //把端口号转化成整数 short port; if((port = atoi(argv[2]))==0){ printf("端口号有误!"); exit(-1); } WSADATA wsa; //初始化套接字DLL if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){ //高字节指定了次版本号,低字节指定了主版本号,两个字节加到一起,就是你想要的Winsock库的版本号了 printf("套接字初始化失败!"); exit(-1); } //创建套接字 SOCKET serverSocket; if((serverSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET){ printf("创建套接字失败!"); exit(-1); } struct sockaddr_in serverAddress; memset(&serverAddress,0,sizeof(sockaddr_in)); serverAddress.sin_family=AF_INET; serverAddress.sin_addr.S_un.S_addr = htonl(INADDR_ANY); serverAddress.sin_port = htons(port); //绑定 if(bind(serverSocket,(sockaddr*)&serverAddress,sizeof(serverAddress))==SOCKET_ERROR){ printf("套接字绑定到端口失败!端口: %d\n",port); exit(-1); } //进入侦听状态 if(listen(serverSocket,SOMAXCONN)==SOCKET_ERROR){ printf("侦听失败!"); exit(-1); } printf("Server %d is listening......\n",port); SOCKET clientSocket[5],maxSocket;//用来和客户端通信的套接字 struct sockaddr_in clientAddress;//用来和客户端通信的套接字地址 memset(&clientAddress,0,sizeof(clientAddress)); int addrlen = sizeof(clientAddress); fd_set fd_read; int i=0; int j; char buf[4096]; char buff[4096]="exit"; while(1) { FD_ZERO(&fd_read); maxSocket=serverSocket; FD_SET(serverSocket,&fd_read); //FD_SET(clientSocket[i-1],&fd_read); for(j=0;j<i;j++) { FD_SET(clientSocket[j],&fd_read); if(maxSocket"); //gets(buff); if(select(maxSocket+1,&fd_read,NULL,NULL,NULL)>0) { if(FD_ISSET(serverSocket,&fd_read)) { if(buff=="") { if((clientSocket[i++]=accept(serverSocket,(sockaddr*)&clientAddress,&addrlen))==INVALID_SOCKET) { printf("接受客户端连接失败!"); exit(-1); } else { for(j=0;j5) { printf("超过最大客户端数"); exit(-1); } } else { int bytes; for(int k=0;k<i;k++) { if(FD_ISSET(clientSocket[k],&fd_read)) { bytes=recv(clientSocket[k],buf,sizeof(buf),0); if(bytes==-1) { //listen(serverSocket,SOMAXCONN); for (int l=k;l<i;l++) clientSocket[l]=clientSocket[l+1]; i--; } /*if(bytes==0) { //printf("fdsdf"); listen(serverSocket,SOMAXCONN); for (int l=k;l0) { buf[bytes]='\0'; printf("Message from %s: %s\n",inet_ntoa(clientAddress.sin_addr),buf); if(send(clientSocket[k],buf,bytes,0)==SOCKET_ERROR) { printf("发送数据失败!"); exit(-1); } } } } } } } //清理套接字占用的资源 WSACleanup(); return 0; } 客户端代码: #include #include #include #pragma comment(lib,"ws2_32.lib") int main(int argc, char* argv[]){ //判断是否输入了IP地址和端口号 if(argc!=4){ printf("Usage: %s IPAddress PortNumber\n",argv[1]); exit(-1); } //把字符串的IP地址转化为u_long unsigned long ip; if((ip=inet_addr(argv[2]))==INADDR_NONE){ printf("不合法的IP地址:%s",argv[1]); exit(-1); } //把端口号转化成整数 short port; if((port = atoi(argv[3]))==0){ printf("端口号有误!"); exit(-1); } printf("Connecting to %s:%d......\n",inet_ntoa(*(in_addr*)&ip),port); WSADATA wsa; //初始化套接字DLL if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){ printf("套接字初始化失败!"); exit(-1); } //创建套接字 SOCKET sock,serverSocket; if((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET){ printf("创建套接字失败!"); exit(-1); } struct sockaddr_in serverAddress; memset(&serverAddress,0,sizeof(sockaddr_in)); serverAddress.sin_family=AF_INET; serverAddress.sin_addr.S_un.S_addr = ip; serverAddress.sin_port = htons(port); //建立和服务器的连接 if(connect(sock,(sockaddr*)&serverAddress,sizeof(serverAddress))==SOCKET_ERROR) { printf("建立连接失败!"); exit(-1); } char buf[4096]; while(1){ printf(">"); //从控制台读取一行数据 gets(buf); if(send(sock,buf,strlen(buf),0)==SOCKET_ERROR){ printf("发送c数据失败!"); exit(-1); } int bytes; if((bytes=recv(sock,buf,sizeof(buf),0))==SOCKET_ERROR) { printf("接收c数据失败!\n"); exit(-1); } else { buf[bytes]='\0'; printf("Message from %s: %s\n",inet_ntoa(serverAddress.sin_addr),buf); } } //清理套接字占用的资源 WSACleanup(); return 0; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值