WinSocket常用函数

1int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData);

为了在应用程序当中调用任何一个Winsock API函数,首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务的初始化,因此需要调用WSAStartup函数。使用Socket的程序在使用Socket之前必须调用WSAStartup函数。该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的Socket的版本信息。当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。

返回值:成功返回0,失败返回错误代码

 

2int PASCAL FAR WSACleanup (void);

终止Winsock 2 DLL (Ws2_32.dll) 的使用.

在一个多线程的环境下,WSACleanup()中止了Windows Sockets在所有线程上的操作.

 

原型in_addr_t  inet_addr(const char *cp);

参数:字符串,一个点分十进制的IP地址

返回值

如果正确执行将返回一个无符号长整型数。如果传入的字符串不是一个合法的IP地址,将返回INADDR_NONE

头文件:Winsock2.h.

 

#include <winsock.h>

u_short PASCAL FAR htons( u_short hostshort);

hostshort:主机字节顺序表达的16位数。

将主机的无符号短整形数转换成网络字节顺序

简单地说,htons()就是将一个数的高低位互换

(:00000111 11011100 ---11011100 00000111)

 

3SOCKET()

我们使用系统调用socket()来获得文件描述符//内核(kernel)利用文件描述符来访问文件,非负整数
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol);

第一个参数domain设置为“AF_INET”
第二个参数是套接口的类型:SOCK_STREAM
SOCK_DGRAM。第三个参数设置为0
系统调用socket()只返回一个套接口描述符,如果出错,则返回-1

4bind()

一旦你有了一个套接口以后,下一步就是把套接口绑定到本地计算机的某一个端口上。但如果你只想使用connect()则无此必要。
下面是系统调用bind()的使用方法:
#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd,struct sockaddr*my_addr,int addrlen);

第一个参数sockfd是由socket()调用返回的套接口文件描述符。
第二个参数my_addr是指向数据结构sockaddr的指针。数据结构sockaddr中包括了关于你的地址、端口和IP地址的信息。
第三个参数addrlen可以设置成sizeof(structsockaddr)
下面是一个例子:
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#defineMYPORT3490
main()
{
int sockfd;
struct sockaddr_inmy_addr;
sockfd=socket(AF_INET,SOCK_STREAM,0);/*do someerror checking! 
系统调用socket()来获得文件描述符*/
my_addr.sin_family=AF_INET;/*hostbyteorder*/
my_addr.sin_port=htons(MYPORT);/*short,network byte order
本机字节顺序转换成网络字节顺序*/
my_addr.sin_addr.s_addr=inet_addr("132.241.5.10");
bzero(&(my_addr.sin_zero),8);/*zero the rest of the struct*/
/*don't forget your error checking for bind():*/
bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr));
...

成功返回0,如果出错,bind()也返回-1
如果你使用connect()系统调用,那么你不必知道你使用的端口号。当你调用connect()时,它检查套接口是否已经绑定,如果没有,它将会分配一个空闲的端口。

5Connect()

系统调用connect()的用法如下:
#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd,struct sockaddr* serv_addr,int addrlen);

第一个参数还是套接口文件描述符,它是由系统调用socket()返回的。
第二个参数是serv_addr是指向数据结构sockaddr的指针,其中包括目的端口和IP地址。
第三个参数可以使用sizeof(structsockaddr)而获得。
下面是一个例子:
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#defineDEST_IP"132.241.5.10"
#defineDEST_PORT23
main()
{
intsockfd;
structsockaddr_indest_addr;/*will hold the destination addr*/
sockfd=socket(AF_INET,SOCK_STREAM,0);/*do some error checking!*/
dest_addr.sin_family=AF_INET;/*hostbyteorder*/
dest_addr.sin_port=htons(DEST_PORT);/*short,network byte order*/
dest_addr.sin_addr.s_addr=inet_addr(DEST_IP);//
将一个点分十进制的IP转换成一个长整型数
bzero(&(dest_addr.sin_zero),8);/*zero the rest of the struct  置字节字符串的前n个字节为零,&表示这个字符串的地址

此函数原型:extern void bzero(void *s,int n)*/
/*don'tforgettoerrorchecktheconnect()!*/
connect(sockfd,(structsockaddr*)&dest_addr,sizeof(struct sockaddr));
...

同样,如果出错,connect()将会返回-1

6listen()

如果你希望不连接到远程的主机,也就是说你希望等待一个进入的连接请求,然后再处理它们。这样,你通过首先调用listen(),然后再调用accept()来实现。
系统调用listen()的形式如下:
int listen(int sockfd,int backlog);
第一个参数是系统调用socket()返回的套接口文件描述符。
第二个参数是进入队列中允许的连接的个数。进入的连接请求在使用系统调用accept()应答之前要在进入队列中等待。这个值是队列中最多可以拥有的请求的个数。大多数系统的缺省设置为20。你可以设置为5或者10。当出错时,listen()将会返回-1值。
当然,在使用系统调用listen()之前,我们需要调用bind()绑定到需要的端口,否则系统内核将会让我们监听一个随机的端口。所以,如果你希望监听一个端口,下面是应该使用的系统调用的顺序:
socket();
bind();
listen();
/*accept()goeshere*/

7accept()

系统调用accept()比较起来有点复杂。在远程的主机可能试图使用connect()连接你使用
listen()正在监听的端口。但此连接将会在队列中等待,直到使用accept()处理它。调用accept()
之后,将会返回一个全新的套接口文件描述符来处理这个单个的连接。这样,对于同一个连接
来说,你就有了两个文件描述符。原先的一个文件描述符正在监听你指定的端口,新的文件描
述符可以用来调用send()recv()
调用的例子如下:
#include<sys/socket.h>
int accept(intsockfd,void*addr,int*addrlen);

第一个参数是正在监听端口的套接口文件描述符。第二个参数addr是指向本地的数据结构
sockaddr_in的指针。调用connect()中的信息将存储在这里。通过它你可以了解哪个主机在哪个
端口呼叫你。第三个参数同样可以使用sizeof(structsockaddr_in)来获得。
如果出错,accept()也将返回-1。下面是一个简单的例子:
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#defineMYPORT3490/*the port users will be connect in gto*/
#defineBACKLOG10/*how many pending connection squeue will hold*/
main()
{
int sockfd,new_fd;/*listen on sock_fd,new connection on new_fd*/
struct sockaddr_inmy_addr;/*my address information*/
struct sockaddr_intheir_addr;/*connector 's address information*/
int sin_size;
sockfd=socket(AF_INET,SOCK_STREAM,0);/*do some error checking!*/
my_addr.sin_family=AF_INET;/*host byte order*/
my_addr.sin_port=htons(MYPORT);/*short,network byte order*/
my_addr.sin_addr.s_addr=INADDR_ANY;/*auto-fill with my IP*/
bzero(&(my_addr.sin_zero),8);/*zero the rest of the struct*/
/*don't forget your error checking for these calls:*/
bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr));
listen(sockfd,BACKLOG);
sin_size=sizeof(structsockaddr_in);
new_fd=accept(sockfd,&their_addr,&sin_size);//
返回一个新的文件描述符
...
下面,我们将可以使用新创建的套接口文件描述符new_fd来调用send()recv()

8send() recv()
系统调用send()的用法如下:
int send(int sockfd,const void* msg,int len,int flags);
第一个参数是你希望给发送数据的套接口文件描述符。它可以是你通过socket()系统调用返回的,也可以是通过accept()系统调用得到的。
第二个参数是指向你希望发送的数据的指针。
第三个参数是数据的字节长度。第四个参数标志设置为0
下面是一个简单的例子:
char *msg="Beejwashere!";
int len,bytes_sent;
..
len=strlen(msg);
bytes_sent=send(sockfd,msg,len,0);
...

系统调用send()返回实际发送的字节数,这可能比你实际想要发送的字节数少。如果返回的字节数比要发送的字节数少,你在以后必须发送剩下的数据。当send()出错时,将返回-1
系统调用recv()的使用方法和send()类似:
int recv(int sockfd,void* buf,int len,unsigned int flags);
第一个参数是要读取的套接口文件描述符。
第二个参数是保存读入信息的地址。
第三个参数是缓冲区的最大长度。第四个参数设置为0
系统调用recv()返回实际读取到缓冲区的字节数,如果出错则返回-1
这样使用上面的系统调用,你可以通过数据流套接口来发送和接受信息。

9sendto() recvfrom()
因为数据报套接口并不连接到远程的主机上,所以在发送数据包之前,我们必须首先给出目的地址,请看:
int sendto(int sockfd,const void* msg,int len,unsigned int flags,
const struct sockaddr *to,int tolen);

除了两个参数以外,其他的参数和系统调用send()时相同。
参数to是指向包含目的IP地址和端口号的数据结构sockaddr的指针。
参数tolen可以设置为sizeof(structsockaddr)
系统调用sendto()返回实际发送的字节数,如果出错则返回-1
系统调用recvfrom()的使用方法也和recv()的十分近似:
int recvfrom(int sockfd,void* buf,int len,unsigned int flags,
struct sockaddr* from,int* fromlen);

参数from是指向本地计算机中包含源IP地址和端口号的数据结构sockaddr的指针。
参数fromlen设置为sizeof(struct sockaddr)
系统调用recvfrom()返回接收到的字节数,如果出错则返回-1

10close() shutdown()
你可以使用close()调用关闭连接的套接口文件描述符
close(sockfd);
这样就不能再对此套接口做任何的读写操作了。
使用系统调用shutdown(),可有更多的控制权。它允许你在某一个方向切断通信,或者切断双方的通信:
int shutdown(int sockfd,int how);
第一个参数是你希望切断通信的套接口文件描述符。第二个参数how值如下:
0—Further receive sared is allowed
1—Further send sared is allowed
2—Further send sand receive sared is allowed(likeclose())
shutdown()
如果成功则返回0,如果失败则返回-1

11getpeername()
这个系统的调用十分简单。它将告诉你是谁在连接的另一端:
#include<sys/socket.h>
int getpeername(int sockfd,struct sockaddr* addr,int* addrlen);

第一个参数是连接的数据流套接口文件描述符。
第二个参数是指向包含另一端的信息的数据结构sockaddr的指针。
第三个参数可以设置为sizeof(structsockaddr)
如果出错,系统调用将返回-1
一旦你获得了它们的地址,你可以使用inet_ntoa()或者gethostbyaddr()来得到更多的信息。 gethostname()


系统调用gethostname()比系统调用getpeername()还简单。它返回程序正在运行的计算机的名字。系统调用gethostbyname()可以使用这个名字来决定你的机器的IP地址。
下面是一个例子:
#include<unistd.h>
int gethostname(char*hostname,size_t size);

如果成功,gethostname将返回0。如果失败,它将返回-1

-----------------------------------------------------------------------------------------------------------------------------------------------------------

基本TCP套接口编程一 
  本文出自:http://sunsland.top263.net 作者: (2001-10-22 12:00:00) 
  概述 
  socket() --得到文件描述符! 
  bind() --我们在哪个端口? 
  connect() --Hello 
  listen() --有人给我打电话吗? 
  accept() --"Thank you for calling port 3490." 
  send() recv() --Talk to me, baby! 
  sendto() recvfrom() --Talk to me, DGRAM-style 
  close() shutdown() --滚开! 
  getpeername() --你是谁? 
  gethostname() --我是谁? 
  DNS --你说白宫,我说 "198.137.240.100" 
  -------------------------------------------------------------------------------- 
  socket函数 
  功能:指定协议类型 
  定义: 
  #include 
  #include 
  int socket(int family, int type, int protocol); 
  返回值 
  出错: -1 
  成功: 套接口文件描述符 (socket file descriptor)(套接字)sockfd 
  socket 函数指定了协议族family(IPv4IPv6unix)和套接口类型(字节流、数据报或原 
  始套接口)。但并没有指定本地协议地址或远程协议地址。 
  理解socket 
  socket使用 Unix 文件描述符 (file descriptor) 和其他程序通讯的方式。 
  Unix 程序在执行任何形式的 I/O 的时候,程序是在读或者写一个文件描述符。 
  一个文件描述符只是一个和打开的文件相关联的整数。 
  这个文件可能是一个网络连接,FIFO(队列),管道,终端,磁盘上的文件或者什么其他 
  的东西。Unix 中所有的东西是文件!因此,与 Internet 上别的程序通讯的时候, 
  要通过文件描述符。利用系统调用 socket()得到网络通讯的文件描述符。他返回 
  套接口描述符 (socket descriptor),然后再通过他来调用 send() recv() 
  那么为什么不用一般的调用 read() write() 来通过套接口通讯? 
  简单的答案是:可以使用一般的函数! 
  详细的答案是:使用 send() recv() 让你更好的控制数据传输。 
  -------------------------------------------------------------------------------- 
  connect 函数 
  功能:建立与TCP服务器的连接 
  定义: 
  #include 
  #include 
  int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); 
  //sockfd 是系统调用 socket() 返回的套接口文件描述符 
  serv_addr 是保存着目的地端口和 IP 地址的数据结构 struct sockaddr 
  //addrlen 设置为 sizeof(struct sockaddr) 
  connect 激发 TCP的三路握手过程 
  服务器必须准备好接受外来的连接。 
  这通过调用socket,bindlisten函数来完成,称为被动打开(Passive open) 
  客户通过调用connect进行主动打开(active opn) 
  这引起客户TCP发送一个SYN分节(表示同步),它告诉服务器,客户将在(待建立的
  连接中发送的数据的初始序列号。 
  服务器必须确认客户的SYN,同时自己也得发送一个SYN分节,它含有服务器将在 
  同一连接中发送的数据的韧始序列号。服务器以单个分节向客户发送SYN和对客户 
  SYNACK。客户必须确认服务器的SYN 
  connect 出错时的返回 
  出错原因1 :未收到SYN的响应(服务器超时,75s) 
  返回值:ETIMEDOUT 
  用户端输出:Connection time out. 
   
  出错原因2 :收到RST响应(Hard error)SYN到达服务器,但该服务器的无此项端口服务 
  返回值:ECONNREFUSE 
  用户端输出:Connection refused 
  出错原因 :ICMP错误:不可路由(soft error)(目的地不可达
  返回值:EHOSTUNREACH 
  用户端输出:ENETUNREACH No route to host 
  -------------------------------------------------------------------------------- 
  bind 函数 
  功能:给套接口分配一个本地协议地址 
  定义: 
  #include 
  #include 
  int bind(int sockfd, const struct sockaddr *my_addr, int addrlen); 
  sockfd 是调用 socket 返回的文件描述符。 
  my_addr 是指向数据结构 struct sockaddr 的指针,保存地址(即端口和 IP 地址) 信息。 
  addrlen 设置为 sizeof(struct sockaddr) 
  返回: 0—成功, -1---出错 
  让内核自动处理地址ip和端口port 
  my_addr.sin_port = 0; /* choose an unused port at random */ 
  my_addr.sin_addr.s_addr = INADDR_ANY; /* use my IP address */ 
  bind( ) 自己选择合适的端口:将0赋给 my_addr.sin_por 
  自动填上他所运行的机器的 IP 地址:my_addr.sin_addr.s_addr 设置为 INADDR_ANY 
   
  -------------------------------------------------------------------------------- 
  listen 函数 
  功能:将未连接主动套接口的转换为被动套接口,指示内核接受对该套接口的连接请求
  CLOSED --? LISTEN 
  定义: 
  #include 
  int listen(int sockfd, int backlog); 
  sockfd 是调用 socket() 返回的套接口文件描述符。 
  backlog 是在进入队列中允许的连接数目。 
  监听套接口的两个队列 
  未完成连接队列(incompleted connection queue): SYN_RECV 
  已完成连接队列(completed connection queue): ESTABLISHED 
  当一个客户的SYN到达时,如两队列都满的, TCP将忽略该分节且不发RST

RSTTCP首部6个标志比特之一,表示重置连接、复位连接)
  -------------------------------------------------------------------------------- 
  ACCEPT 函数 
  功能:在已完成队列头返回下一个已完成的连接 
  定义 
  #include 
  int accept(int sockfd, struct sockaddr *cliaddr, int* addrlen); 
  调用成功时返回: 1. cliaddr: 客户进程的协议地址和地址大小 2. 新套接口描述字 
  (已连接套接口描述字
  监听套接口描述字 listening socket descriptor 
  一个给定的服务器常常是只生成一个监听套接口, 且一直存在,直到该服务器关闭。 
  已连接套接口描述字connected socket descriptor 
  内核为每个被接受的客户连接创建了一个已连接套接口。当服务器完成某客户的服务时, 
  关闭已连接套接口 
  1024以下的端口:超级用户使用 
  -------------------------------------------------------------------------------- 
  fork 函数 
  功能:派生新进程 create new process 
  定义
  #include 
  pid_t fork (void); //pid_t是进程号类型,实质是宏定义的unsigned int类型
  在子进程中返回0,在父进程中返回子进程的进程ID 
  出错时返回 –1,调用一次返回两次 
  fork的典型应用: 
  1.一个进程可为自己创建一个拷贝。当一个拷贝处理一个操作时,其他的拷贝可以 
  执行其他的任务。这是非常典型的网络服务器。 
  2.一个进程想执行其他的程序,由于创建新进程的唯一方法是调用fork,进程首先 
  调 fork来生成一个拷贝,然后其中一个拷贝(通常为子进程)调用exec 来代替自己 
  去执行新程序。 
  (http://www.fanqiang.com) 
   
  ----------------------------------------------------------------------------------------------------------------------------- 
  ----------------------------------------------------------------------------------------------------------------------------- 
  并发服务器 
  迭代服务器 iterative server 单进程 
  并发服务器 concurrent server 多进程 
  当连接建立时,accept返回,服务器调用fork 
  子进程为客户提供服务(通过connfd已连接套接口) 
  父进程等待另一个连接(通过listenfd监听套接口) 
  子进程开始处理新客户后,父进程便关闭已连接套接口。 
  每个文件或套接口都有一个访问计数,该访问计数在文件表项中维护,它表示当前指 
  向该文件或套接口的打开的描述字个数。 
  从sock返回后,与listenfd关联的文件表项访问计数值为1 
  从accept返回后,与connfd关联的文件表项访问计数值也为1 
  当fork返回后,两个描述字在父进程与子进程间共享(duplicated)与两个套接门相关 
  联的文件表项访问计数值均为2 
  当父进程关闭connfd时,只是将访问计数值从2减为1。描述字只在访问计数值达到0 
  才真正关闭,这在后面某个时候子进程关闭connfd时会碰上
   
  -------------------------------------------------------------------------------- 
  close 函数 
  功能:将套接口做上已关闭的标记,并立即返回进程。 
  将套接口描述字的访问计数器减1 
  当访问计数器值为0时,引发TCP的四个分组连接终止序列,从而关闭套接口。 
  定义: 
  #include 
  int close(int sockfd); 
   
  -------------------------------------------------------------------------------- 
  getsockname getpeername函数 
  功能: 
  getsockname: 返回本地协议地址 
  getpeername:返回远程协议地址 
  定义: 
  #include 
  int getsockname (int sockfd, struct sockaddr *localaddr, int *addrlen); 
  int getpeername(int sockfd, struct sockaddr *peeraddr, int *addrlen); 
  -------------------------------------------------------------------------------- 
  一个示例程序
  #include 
  #include 
  #include 
  #define MYPORT 3490 /* the port users will be connecting to */ 
  #define BACKLOG 10 /* how many pending connections queue will hold */ 
  main() 
  
  int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */ 
  struct sockaddr_in my_addr; /* my address information */ 
  struct sockaddr_in their_addr; /* connector's address information */ 
  int sin_size; 
  sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */ 
  my_addr.sin_family = AF_INET; /* host byte order */ 
  my_addr.sin_port = htons(MYPORT); /* short, network byte order */ 
  my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */ 
  bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */ 
  /* don't forget your error checking for these calls: */ 
  bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)); 
  listen(sockfd, BACKLOG); 
  sin_size = sizeof(struct sockaddr_in); 
  new_fd = accept(sockfd, &their_addr, &sin_size);

 

12HANDLE CreateThread

(

LPSECURITY_ATTRIBUTES lpThreadAttributes,      //线程的安全属性

SIZE_T dwStackSize,                                                       //线程堆栈大小

LPTHREAD_START_ROUTINE lpStartAddress,                  //线程函数的指针

LPVOID lpParameter,                                                     //传给线程函数的参数

DWORD dwCreationFlags,                                            //创建标志 CREATE_SUSPENDED:创建一个挂起的线程;0:创建后立即激活

LPDWORD lpThreadId                                                   //若不想返回线程ID,设置值为NULL

)

返回值:函数成功,返回线程句柄;函数失败返回false

 

13BOOL   GetExitCodeThread

(

       HANDLE         hThread,                  // in,线程handle,也就是CreateThread()的返回值

       LPDWORD      lpExitCode               //out,存储线程结束代码,也就是线程的返回值

);

说明: 此函数调用成功返回TRUE,失败返回FALSE,只表示这个函数是否调用成功而己.

        不能根据返回值来判断一个线程是否结束,而要根据 lpExitCode的值来确定,

        lpExitCode  STILL_ACTIVE 表示线程正在运行.

            若线程己经结束,lpExitCode中存储指定线程的返回值.

14select函数

select()的机制中提供一fd_set数据结构,实际上是一long类型的数组 每一个数组元素都能与一打开的文件句柄(不管是Socket句柄,还是其他 文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成, 当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执 行了select()的进程哪一Socket或文件可读。主要用于Socket通信当中!

a非阻塞式I/O编程特点:编辑

一、如果一个发现I/O有输入,读取的过程中,另外一个也有了输入,这时候不会产生任何反应.这就需要你的程序语句去用到select函数的时候才知道有数据输入。

二、程序去select的时候,如果没有数据输入,程序会一直等待,直到有数据为止,也就是程序中无需循环和sleep

SelectSocket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connectacceptrecvrecvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。

可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生,则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常

返回值:准备就绪的描述符数,若超时则返回0,若出错则返回-1

b操作程序

下面具体解释:

#include <sys/types.h>

#include <sys/times.h>

#include <sys/select.h>

int select(nfds, readfds, writefds, exceptfds, timeout)

int nfds;

fd_set *readfds, *writefds, *exceptfds;

struct timeval *timeout;

select的参数:

ndfsselect监视的文件句柄数,视进程中打开的文件数而定,一般设为你要监视各文件

中的最大文件号加一。

readfdsselect监视的可读文件句柄集合。

writefds: select监视的可写文件句柄集合。

exceptfdsselect监视的异常文件句柄集合。

timeout:本次select()的超时结束时间。(见/usr/sys/select.h,可精确至百万分之一秒!)

readfdswritefds中映象的文件可读或可写或超时,本次select()

就结束返回。程序员利用一组系统提供的宏在select()结束时便可判

断哪一文件可读或可写,对Socket编程特别有用的就是readfds

c宏解释

几行相关的宏解释如下:

FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。

FD_SET(int fd, fd_set *fdset):建立文件句柄fdfdset的联系。

FD_CLR(int fd, fd_set *fdset):清除文件句柄fdfdset的联系。

FD_ISSET(int fd, fd_set *fdset):检查fdset联系的文件句柄fd是否

可读写,当>0表示可读写。

(关于fd_set及相关宏的定义见/usr/include/sys/types.h

dsocket读写

这样,你的socket只需在有东东读的时候才读入,大致如下:

...

int sockfd;

fd_set fdR;

struct timeval timeout = ..;

...

for(;;) {

FD_ZERO(&fdR);

FD_SET(sockfd, &fdR);

switch (select(sockfd + 1, &fdR, NULL, NULL , &timeout)) {

case -1:

error handled by u;

break;

case 0:

timeout hanled by u;

break;

default:

if (FD_ISSET(sockfd, &fdR)) {

now u read or recv something;

/* if sockfd is father and server socket, u can now accept() */

}

}

}

所以一个FD_ISSET(sockfd)就相当通知了sockfd可读。

struct timeval {

long tv_sec; //

long tv_usec; // 微秒,1s=1000000us

};

不同的timeval设置使select()表现出超时结束、无超时阻塞和轮询三种特性:

若将NULL形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为00毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行;第三,timeout的值大于0,这就是等待的超时时间,即selecttimeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回。

select返回值:

-1select错误

正值:某些文件可读写或出错

0:等待超时,没有可读写或错误的文件

 

15int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

 

参数:

sockfd:标识一个套接口的描述字。

level:选项定义的层次;支持SOL_SOCKETIPPROTO_TCPIPPROTO_IPIPPROTO_IPV6

optname:需设置的选项。

optval指针,指向存放选项待设置的新值的缓冲区

optlenoptval缓冲区长度。

返回值:

若无错误发生,setsockopt()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码

错误代码

WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()

WSAENETDOWNWINDOWS套接口实现检测到网络子系统失效。

WSAEFAULToptval不是进程地址空间中的一个有效部分。

WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。

WSAEINVALlevel值非法,或optval中的信息非法。

WSAENETRESET:当SO_KEEPALIVE设置后连接超时。

WSAENOPROTOOPT:未知或不支持选项。其中,SOCK_STREAM类型的套接口不支持SO_BROADCAST选项,SOCK_DGRAM类型的套接口不支持SO_DONTLINGER SO_KEEPALIVESO_LINGERSO_OOBINLINE选项。

WSAENOTCONN:当设置SO_KEEPALIVE后连接被复位。

WSAENOTSOCK:描述字不是一个套接口

 

用法实例:

1.设置调用closesocket(),仍可继续重用该socket。调用closesocket()一般不会立即关闭socket,而经历TIME_WAIT的过程。

BOOL bReuseaddr = TRUE;

setsockopt( s, SOL_SOCKET, SO_REUSEADDR, ( const char* )&bReuseaddr, sizeof( BOOL ) );

2. 如果要已经处于连接状态的soket在调用closesocket()后强制关闭,不经历TIME_WAIT的过程:

BOOL bDontLinger = FALSE;

setsockopt( s, SOL_SOCKET, SO_DONTLINGER, ( const char* )&bDontLinger, sizeof( BOOL ) );

3.send(),recv()过程中有时由于网络状况等原因,收发不能预期进行,可以设置收发时限:

int nNetTimeout = 1000; //1

//发送时限

setsockoptsocket, SOL_SOCKET, SO_SNDTIMEO, ( char * )&nNetTimeout, sizeof( int ) );

//接收时限

setsockoptsocket, SOL_SOCKET, SO_RCVTIMEO, ( char * )&nNetTimeout, sizeof( int ) );

4.send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节(异步);系统默认的状态发送和接收一次为8688字节(

8.5K);在实际的过程中如果发送或是接收的数据量比较大,可以设置socket缓冲区,避免send(),recv()不断的循环收发:

// 接收缓冲区

int nRecvBufLen = 32 * 1024; //设置为32K

setsockopt( s, SOL_SOCKET, SO_RCVBUF, ( const char* )&nRecvBufLen, sizeof( int ) );

//发送缓冲区

int nSendBufLen = 32*1024; //设置为32K

setsockopt( s, SOL_SOCKET, SO_SNDBUF, ( const char* )&nSendBufLen, sizeof( int ) );

5.在发送数据的时,不执行由系统缓冲区socket缓冲区的拷贝,以提高程序的性能:

int nZero = 0;

setsockoptsocket, SOL_SOCKET, SO_SNDBUF, ( char * )&nZero, sizeof( nZero ) );

6.在接收数据时,不执行将socket缓冲区的内容拷贝到系统缓冲区:

int nZero = 0;

setsockopt( s, SOL_SOCKET, SO_RCVBUF, ( char * )&nZero, sizeof( int ) );

7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:

BOOL bBroadcast = TRUE;

setsockopt( s, SOL_SOCKET, SO_BROADCAST, ( const char* )&bBroadcast, sizeof( BOOL ) );

8.client连接服务器过程中,如果处于非阻塞模式下的socketconnect()的过程中可以设置connect()延时,直到accpet()被调用(此设置只有在非阻塞的过程中有显著的作用,在阻塞的函数调用中作用不大)

BOOL bConditionalAccept = TRUE;

setsockopt( s, SOL_SOCKET, SO_CONDITIONAL_ACCEPT, ( const char* )&bConditionalAccept, sizeof( BOOL ) );

9.如果在发送数据的过程中send()没有完成,还有数据没发送,而调用了closesocket(),以前一般采取的措施是shutdown(s,SD_BOTH),但是数

据将会丢失。

某些具体程序要求待未发送完的数据发送出去后再关闭socket,可通过设置让程序满足要求:

struct linger {

u_short l_onoff;

u_short l_linger;

};

struct linger m_sLinger;

m_sLinger.l_onoff = 1; //在调用closesocket()时还有数据未发送完,允许等待

// m_sLinger.l_onoff=0;则调用closesocket()后强制关闭

m_sLinger.l_linger = 5; //设置等待时间为5

setsockopt( s, SOL_SOCKET, SO_LINGER, (const char*)&m_sLinger, sizeof(struct linger));

 

16WSACreateEvent

#include <winsock2.h>

WSAEVENT WSAAPI WSACreateEvent( VOID );

功能:

创建事件句柄

返回值:

如果函数成功,则返回值即是事件对象的句柄。

如果函数失败,返回WSA_INVALID_EVENT。应用程序可通过调用WSAGetLastError()函数获取进一步的错误信息

错误代码

WSANOTINITIALISED 在调用本API之前应成功调用WSAStartup()

WSAENETDOWN 网络子系统失效。

WSA_NOT_ENOUGH_MEMORY 无足够内存创建事件对象

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值