网络编程(转)<下>

设置和获得套接口选项
获得套接口选项
int getsockopt ( int sockfd, int level, int optname, void * optval, socklen_t *opteln ) 
设置套接口选项:
  int setsockopt ( int sockfd, int level, int optname, const void * optval, socklen_t *opteln )  
参数含意
sockfd(套接字): 指向一个打开的套接口描述字 
level:(级别): 指定选项代码的类型。 
SOL_SOCKET: 基本套接口 
IPPROTO_IP: IPv4套接口 
IPPROTO_IPV6: IPv6套接口 
IPPROTO_TCP: TCP套接口 
optname(选项名): 选项名称 
optval(选项值): 是一个指向变量的指针 类型:整形,套接口结构, 其他结构类型:linger{}, timeval{ } 
optlen(选项长度) :optval 的大小 
返回值:标志打开或关闭某个特征的二进制选项
用于设置SOCKET细节
SO_REUSEADDR 重用地址
当打开某一端口的程序非正常退出,可能端口仍被占用,第二次执行程序就会报”Addr in use”无法使用这一端口
用SO_REUSEADDR 可以防止这一问题.服务器的socket,最好用这一选项.
 
n = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
val = 1;
setsockopt(n, SOL_SOCKET, SO_REUSEADDR, (char *) &val, sizeof (val));
// some code ...
if ((bind(n, (struct sockaddr *) &sin, sizeof (sin)) < 0)
    || (listen(n, QLEN) < 0))
    exit(1);
SO_BROADCAST UDP广播选项
一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性
用sendto发送时,广播地址可以写
from.sin_addr.s_addr=INADDR_BROADCAST;
或是根据IP地址和掩码算出的子网的广播地址
 
int bBroadcast=1; setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(int));
UDP广播实例
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <errno.h>
#include <stdlib.h>
#include <arpa/inet.h>
 
int main(int argc, char **argv)
{
struct sockaddr_in s_addr;
int sock;
int addr_len;
int len;
char buff[128];
int yes;
 
/* 创建 socket */
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket");
exit(errno);
} else
printf("create socket./n/r");
 
/* 设置通讯方式对广播,即本程序发送的一个消息,网络上所有主机均可以收到 */
yes = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes));
/* 唯一变化就是这一点了 */
 
/* 设置对方地址和端口信息 */
s_addr.sin_family = AF_INET;
if (argv[2])
s_addr.sin_port = htons(atoi(argv[2]));
else
s_addr.sin_port = htons(7838);
//Windows
if (argv[1])
s_addr.sin_addr.s_addr = inet_addr(argv[1]);
else {
printf("消息必须有一个接收者!/n");
exit(0);
}
 
/* 发送UDP消息 */
addr_len = sizeof(s_addr);
strcpy(buff, "hello i'm here");
len = sendto(sock, buff, strlen(buff), 0,
(struct sockaddr *) &s_addr, addr_len);
if (len < 0) {
printf("/n/rsend error./n/r");
return 3;
}
 
printf("send success./n/r");
return 0;
}
文件下载
文件下载一般采用TCP进行设计,这样的优点程序设计相对简单,防止文件内容在下载时丢失.
假设设计是从服务器下载一个文件到客户端,在TCP打开时,服务器首先去读文件信息,如文件名,文件长度等,将其写入一个指定长度的结构头里,发往客户端,然后将文件内容依次发往客户端.
客户端首先收下定长的结构体,从中读取文件名和文件长度.然后连续接收文件长度的字符,即可将文件完整接收下来.
由于TCP是不会丢失数据,因此只有二种可能,一种是接收失败,一种是把文件完整接收下来.
文件的阻塞函数
一般文件读取函数read,socket的接收函数recv,recvfrom都是阻塞型函数,即没有数据收到时,整个程序被阻塞这个函数
在服务器软件上,经常要接收多个客户端的数据.如果单纯采用recv造成整个程序的阻塞.
一种方法采用多线程.
更为常用是采用多路复用函数select来同时控制多个socket/file 描述符.
select多路复用
select系统调用是用来让我们的程序监视多个文件句柄(file descrīptor)的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有某一个或多个发生了状态改变。
普通文件读写
socket的收发
设备文件的收发
fd_set数据结构
select主要操作fd_set的数据结构.fd_set是一个文件描述符的矢量数组
大体上可以把fd_set看成一个只有32项的整数数组.
每一个socket或fd都是fd_set中的一项,
一般采用一组宏来操作fd_set
void FD_SET(int fd,fd_set *fdset)
 void FD_CLR(int fd,fd_set *fdset)
 FD_CLR将fd从fdset里面清除 
 void FD_ZERO(fd_set *fdset)
FD_ZERO从fdset中清除所有的文件描述符 
 int FD_ISSET(int fd,fd_set *fdset)
 FD_ISSET判断fd是否在fdset集合中
 
 
 
 
typedef struct fd_set     {             u_int fd_count;             int fd_array[fd_setsize];     }
select定义
int select(int max_fd, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
其中max_fd为我们要监听的套接字中值最大的一个,同时在调用select是要将其加1,
readfd即为我们监听的要进行读操作的套接字连接集合,
第三个参数是我们监听的要进行写操作的套接字连接集合,
第四个参数用于异常,而最后一个参数可以用来设定超时,这里同样使用了struct timeval结构,
当有文件被写时,返回一个大于0值,出错返回一个负数,等于表示在timeout的时间,没有任何读写,select是超时返回的
 
select()的使用
select同时监控多个激活的socket(最大值一般为1024)
当相应的socket上有数据接收时,select将其值写入readfd值中.并返回一个大于0值.
这样通过FD_ISSET可以查出是哪一个socket被读写.因为只有一个阻塞点.大大提高程序的性能
因为带有超时机制,也能防止长时间阻塞导致程序无法响应的后果
Select也能处理一般的文件或设备文件,如把标准输入或普通文件加入到监控的集合中
select实例
int main()     {          int ret;          fd_set fds;          struct timeval tv;              FD_ZERO(&fds);          FD_SET(0,&fds);//把标准输入加入监控
        tv.tv_sec = 5;
        tv.tv_usec = 0;
          ret = select(1, &fds, NULL, NULL, &tv);
      if(ret < 0)
      { perror(“select”); exit(-1)
      }else if(ret == 0)
      {//5 秒钟内用户没有按下键
        printf(“timeout”);
      }       else
      {  // 读入用户输入              scanf("%s", buf);       }      }
Select的socket下使用流程
Select 的使用是固定的流程
socket(...);   bind(...);   listen(...);              while(1)         {         FD_ZERO(…)            FD_SET(…)             select(…);                 // 如果是服务器侦听套接字被触发,说明一个新的连接请求建立
            if(FD_ISSET(svr_fd,…))              {                  // 建立新的客户联接连接
       new_fd = accept(…) ;
                   //  加入到监听文件描述符中去;
                       FD_SET(new_fd,…)              }          else
                        {                // 是一个客户端操作           
                              进行操作(read或者write);                  }                                  }   
 
========================================================
本文来源:
原文地址:https://www.cnblogs.com/wainiwann/p/2569563.html