C语言之网络编程(SOCKET)

网络编程之SOCKET

Socket在所有网络操作系统和网络应用程序中都是必不可少的,它是网络通信中应用进程和网络协议之间的接口。在Linux操作系统中,socket属于文件系统的一部分,网络通信可以看作是对文件的读取。这就使得用户对网络的控制像对文件的控制一样方便。

要了解socket就必须要了解一些基本的概念,如:套接口、网络编程的结构等。下面分别讲述这些概念。

一、基本概念:

1.套接口:

简单地说,套接口就是一种使用UNIX系统中的文件描述符和系统进程通信的一种方法。因为在UNIX系统中,所有的 I/O操作都是通过读写文件描述符而产生的。文件描述符就是一个和打开的文件相关连的整数。但文件可以是一个网络连接、一个FIFO、一个管道、一个终 端、一个真正存储在磁盘上的文件或者UNIX系统中的任何其他的东西。所以,如果你希望通过Internet和其他的程序进行通信,你只有通过文件描述 符。使用系统调用socket(),你可以得到socket()描述符。然后你可以使用send()和recv()调用而与其他的程序通信。你也可以使用 一般的文件操作来调用read()和write()而与其他的程序进行通信,但send()和recv()调用可以提供一种更好的数据通信的控制手段。

2.Internet套接口

有两种最常用的Internet套接口,“数据流套接口”和“数据报套接口”,我们用“SOCK_STREAM”和 “SOCK_DGRAM”分别代表上面两种套接口。数据报套接口有时也叫做“无连接的套接口”。数据流套接口是可靠的双向连接的通信数据流。如果你在套接 口中以“ 1, 2”的顺序放入两个数据,它们在另一端也会以“1, 2”的顺序到达。它们也可以被认为是无错误的传输。经常使用的telnet应用程序就是使用数据流套接口的一个例子。使用HTTP的WWW浏览器也使用数 据流套接口来读取网页。事实上,如果你使用telnet登录到一个WWW站点的80端口,然后键入“GET网页名”,你将可以得到这个HTML页。数据流 套接口使用TCP得到这种高质量的数据传输。数据报套接口使用UDP,所以数据报的顺序是没有保障的。数据报是按一种应答的方式进行数据传输的。

       3.数据结构

  下面我们要讨论使用套接口编写程序可能用到的数据结构。

struct sockaddr

{

unsigned short int sa_family;

char sa_data[14];

};

sa_family 为调用socket()时的domain参数,即AF_xxxx值。

sa_data最多使用14个字符长度。

此sockaddr结构会因使用不同的socket domain而有不同结构定义,例如使用AF_INET domain,其socketaddr结构定义便为:

struct socketaddr_in

{

unsigned short int sin_family;

uint16_t sin_port;

struct in_addr sin_addr;

unsigned char sin_zero[8];

};

struct in_addr

{

uint32_t s_addr;

};

sin_family 即为sa_family

sin_port 为使用的port编号

sin_addr.s_addr 为IP地址

sin_zero 填充0以保持与struct sockaddr大小一致。

这个数据结构使得使用其中的各个元素更为方便。要注意的是sin_zero应该使用bzero() 或者memset()而设置为全0。另外,一个指向sockaddr_in数据结构的指针可以投射到一个指向数据结构sockaddr的指针,反之亦然。

4.网络字节顺序

  一个网络可能由不同的体系结构的CPU组成,这些不同体系的CPU使用的字节顺序不同,有的CPU使用big_endian(大 端,在存储器中高字节存储在后),有的CPU使用little_endian(小端,在存储器中高字节存储在前)。因此为了在网络间能够进行数据交换,需 要对这些不同的字节顺序进行处理。

下面是几个字节顺序转换函数:

htons():表示“Host to Network Short”,把主机地址字节顺序转向网络字节顺序(对短整型数据操作)。

htonl():表示“Host to Network Long”,把主机地址字节顺序转向网络字节顺序(对长整型数据操作)。

ntohs():表示“Network to Host Short”,把网络字节顺序转向主机地址字节顺序(对短整型数据操作)。

ntohl():表示“Network to Host Short”,把网络字节顺序转向主机地址字节顺序(对长整型数据操作)。

二、SOCKET通信常用API

1.socket(建立一个socket通信)    相关函数 accept,bind,connect,listen

表头文件

#include<sys/types.h>
#include<sys/socket.h>

定义函数

int socket(int domain,int type,int protocol);

函数说明

socket()用来建立一个新的socket,也就是向系统注册,通知系统建立一通信端口。参数domain 指定使用何种的地址类型,完整的定义在/usr/include/bits/socket.h 内,底下是常见的协议:
PF_UNIX/PF_LOCAL/AF_UNIX/AF_LOCAL UNIX 进程通信协议
PF_INET?AF_INET Ipv4网络协议
PF_INET6/AF_INET6 Ipv6 网络协议
PF_IPX/AF_IPX IPX-Novell协议
PF_NETLINK/AF_NETLINK 核心用户接口装置
PF_X25/AF_X25 ITU-T X.25/ISO-8208 协议
PF_AX25/AF_AX25 业余无线AX.25协议
PF_ATMPVC/AF_ATMPVC 存取原始ATM PVCs
PF_APPLETALK/AF_APPLETALK appletalk(DDP)协议
PF_PACKET/AF_PACKET 初级封包接口

参数

type有下列几种数值:
SOCK_STREAM 提供双向连续且可信赖的数据流,即TCP。支持
OOB 机制,在所有数据传送前必须使用connect()来建立连线状态。
SOCK_DGRAM 使用不连续不可信赖的数据包连接
SOCK_SEQPACKET 提供连续可信赖的数据包连接
SOCK_RAW 提供原始网络协议存取
SOCK_RDM 提供可信赖的数据包连接
SOCK_PACKET 提供和网络驱动程序直接通信。
protocol用来指定socket所使用的传输协议编号,通常此参考不用管它,设为0即可。

返回值

成功则返回socket处理代码,失败返回-1。

错误代码

EPROTONOSUPPORT 参数domain指定的类型不支持参数type或protocol指定的协议
ENFILE 核心内存不足,无法建立新的socket结构
EMFILE 进程文件表溢出,无法再建立新的socket
EACCESS 权限不足,无法建立type或protocol指定的协议
ENOBUFS/ENOMEM 内存不足
EINVAL 参数domain/type/protocol不合法

2.bind(对socket定位)   相关函数 socket,accept,connect,listen

表头文件

#include<sys/types.h>
#include<sys/socket.h>

定义函数

int bind(int sockfd,struct sockaddr * my_addr,int addrlen);

函数说明

bind()用来设置给参数sockfd的socket一个名称。

参数

addrlen为sockaddr的结构长度。

返回值

成功则返回0,失败返回-1,错误原因存于errno中。

错误代码

EBADF 参数sockfd 非合法socket处理代码。
EACCESS 权限不足
ENOTSOCK 参数sockfd为一文件描述词,非socket。

3. connect(建立socket连线)   相关函数 socket,bind,listen

表头文件

#include<sys/types.h>
#include<sys/socket.h>

定义函数

int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);

函数说明

connect()用来将参数sockfd 的socket 连至参数serv_addr 指定的网络地址。结构sockaddr请参考bind()。参数addrlen为sockaddr的结构长度。

返回值

成功则返回0,失败返回-1,错误原因存于errno中。

错误代码

EBADF 参数sockfd 非合法socket处理代码
EFAULT 参数serv_addr指针指向无法存取的内存空间
ENOTSOCK 参数sockfd为一文件描述词,非socket。
EISCONN 参数sockfd的socket已是连线状态
ECONNREFUSED 连线要求被server端拒绝。
ETIMEDOUT 企图连线的操作超过限定时间仍未有响应。
ENETUNREACH 无法传送数据包至指定的主机。
EAFNOSUPPORT sockaddr结构的sa_family不正确。
EALREADY socket为不可阻断且先前的连线操作还未完成。

范例

/* 此程序会连线TCP server,并将键盘输入的字符串传送给server。*/

#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#define PORT 1234
#define SERVER_IP “127.0.0.1”
int main(void)
{

int s;

struct sockaddr_in addr;

char buffer[256];

if((s = socket(AF_INET,SOCK_STREAM,0))<0)

{

perror(“socket”);

exit(1);

}

/* 填写sockaddr_in结构*/

bzero(&addr,sizeof(addr));

addr.sin_family = AF_INET;

addr.sin_port=htons(PORT);

addr.sin_addr.s_addr = inet_addr(SERVER_IP);

/* 尝试连线*/

if(connect(s,&addr,sizeof(addr))<0)

{

perror(“connect”);

exit(1);

}

/* 接收由server端传来的信息*/

recv(s,buffer,sizeof(buffer),0);

printf(“%sn”,buffer);

while(1)

{

bzero(buffer,sizeof(buffer));

/* 从标准输入设备取得字符串*/

read(STDIN_FILENO,buffer,sizeof(buffer));

/* 将字符串传给server端*/

if(send(s,buffer,sizeof(buffer),0)<0)

{

perror(“send”);

exit(1);

}

}

}

执行

$ ./connect

Welcome to server!

hi I am client! /*键盘输入*/

/*<Ctrl+C>中断程序*/

4. accept(接受socket连线)    相关函数 socket,bind,listen,connect

表头文件

#include<sys/types.h>
#include<sys/socket.h>

定义函数

int accept(int s,struct sockaddr * addr,int * addrlen);

函数说明

accept()用来接受参数s的socket连线。参数s的socket必需先经bind()、listen()函数 处理过,当有连线进来时 accept()会返回一个新的socket处理代码,往后的数据传送与读取就是经由新的socket处理,而原来参数s的socket能继续使用 accept()来接受新的连线要求。连线成功时,参数addr所指的结构会被系统填入远程主机的地址数据,参数addrlen为scokaddr的结构 长度。关于结构sockaddr的定义请参考bind()。

返回值

成功则返回新的socket处理代码,失败返回-1,错误原因存于errno中。

错误代码

EBADF 参数s 非合法socket处理代码。
EFAULT 参数addr指针指向无法存取的内存空间。
ENOTSOCK 参数s为一文件描述词,非socket。
EOPNOTSUPP 指定的socket并非SOCK_STREAM。
EPERM 防火墙拒绝此连线。
ENOBUFS 系统的缓冲内存不足。
ENOMEM 核心内存不足。

5. listen(等待连接)  相关函数 socket,bind,accept,connect

表头文件

#include<sys/socket.h>

定义函数

int listen(int s,int backlog);

函数说明

listen()用来等待参数s 的socket连线。参数backlog指定同时能处理的最大连接要求,如果连接数目达此上限则client端将收到ECONNREFUSED的错误。 Listen()并未开始接收连线,只是设置socket为listen模式,真正接收client端连线的是accept()。通常listen()会 在socket(),bind()之后调用,接着才调用accept()。

返回值

成功则返回0,失败返回-1,错误原因存于errno

附加说明

listen()只适用SOCK_STREAM或SOCK_SEQPACKET的socket类型。如果socket为AF_INET则参数backlog 最大值可设至128。

错误代码

EBADF 参数sockfd非合法socket处理代码
EACCESS 权限不足
EOPNOTSUPP 指定的socket并未支援listen模式。

范例

#include<sys/types.h>

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

#include<unistd.h>

#define PORT 1234

#define MAXSOCKFD 10

int main(void)

{

int sockfd,newsockfd,is_connected[MAXSOCKFD],fd;

struct sockaddr_in addr;

int addr_len = sizeof(struct sockaddr_in);

fd_set readfds;

char buffer[256];

char msg[ ] =”Welcome to server!”;

if ((sockfd = socket(AF_INET,SOCK_STREAM,0))<0)

{

perror(“socket”);

exit(1);

}

bzero(&addr,sizeof(addr));

addr.sin_family =AF_INET;

addr.sin_port = htons(PORT);

addr.sin_addr.s_addr = htonl(INADDR_ANY);

if(bind(sockfd,&addr,sizeof(addr))<0)

{

perror(“connect”);

exit(1);

}

if(listen(sockfd,3)<0)

{

perror(“listen”);

exit(1);

}

for(fd=0;fd<MAXSOCKFD;fd++)

is_connected[fd]=0;

while(1)

{

FD_ZERO(&readfds);

FD_SET(sockfd,&readfds);

for(fd=0;fd<MAXSOCKFD;fd++)

{

if(is_connected[fd])

FD_SET(fd,&readfds);

}

if(!select(MAXSOCKFD,&readfds,NULL,NULL,NULL))

continue;

for(fd=0;fd<MAXSOCKFD;fd++)

{

if(FD_ISSET(fd,&readfds))

{

if(sockfd = =fd)

{

if((newsockfd = accept (sockfd,&addr,&addr_len))<0)

perror(“accept”);

write(newsockfd,msg,sizeof(msg));

is_connected[newsockfd] =1;

printf(“cnnect from %sn”,inet_ntoa(addr.sin_addr));

}

else

{

bzero(buffer,sizeof(buffer));

if(read(fd,buffer,sizeof(buffer))<=0)

{

printf(“connect closed.n”);

is_connected[fd]=0;

close(fd);

}

else

printf(“%s”,buffer);

}

}

}

}

执行

$ ./listen

connect from 127.0.0.1

hi I am client

connected closed.

6. select()

select()系统调用可以使进程检测同时等待的多个I/O设备,当没有设备准备好时,select()阻塞,其中任一设备准备好时,select()就返回。

select()的调用形式为:

#include <sys/select.h>

#include <sys/time.h>

int select(int maxfd, fd_set *readfds, fd_set *writefds, fe_set *exceptfds, const struct timeval *timeout);

参数:

select 的第一个参数是文件描述符集中要被检测的比特数,这个值必须至少比待检测的最大文件描述符大1;参数readfds指定了被读监控的文件描述符集;参数 writefds指定了被写监控的文件描述符集;而参数exceptfds指定了被例外条件监控的文件描述符集。

参数timeout起了定时器的作用:到了指定的时间,无论是否有设备准备好,都返回调用。timeval的结构定义如下:

struct timeval{

long tv_sec; //表示几秒

long tv_usec; //表示几微妙

}

timeout取不同的值,该调用就表现不同的性质:

1.timeout为0,调用立即返回;

2.timeout为NULL,select()调用就阻塞,直到知道有文件描述符就绪;

3.timeout为正整数,就是一般的定时器。

select调用返回时,除了那些已经就绪的描述符外,select将清除readfds、writefds和exceptfds中的所有没有就绪的描述符。select的返回值有如下情况:

1.正常情况下返回就绪的文件描述符个数;

2.经过了timeout时长后仍无设备准备好,返回值为0;

3.如果select被某个信号中断,它将返回-1并设置errno为EINTR。

4.如果出错,返回-1并设置相应的errno。

 

8.send(经socket传送数据)  相关函数  sendto,sendmsg,recv,recvfrom,socket

表头文件

#include<sys/types.h>

#include<sys/socket.h>

定义函数

int send(int s,const void * msg,int len,unsigned int falgs);

函数说明

send()用来将数据由指定的socket 传给对方主机。参数s为已建立好连接的socket。参数msg指向欲连线的数据内容,参数len则为数据长度。参数flags一般设0,其他数值定义如下

MSG_OOB 传送的数据以out-of-band 送出。

MSG_DONTROUTE 取消路由表查询

MSG_DONTWAIT 设置为不可阻断运作

MSG_NOSIGNAL 此动作不愿被SIGPIPE 信号中断。

返回值 

成功则返回实际传送出去的字符数,失败返回-1。错误原因存于errno

错误代码

EBADF 参数s 非合法的socket处理代码。

EFAULT 参数中有一指针指向无法存取的内存空间

ENOTSOCK 参数s为一文件描述词,非socket。

EINTR 被信号所中断。

EAGAIN 此操作会令进程阻断,但参数s的socket为不可阻断。

ENOBUFS 系统的缓冲内存不足

ENOMEM 核心内存不足

EINVAL 传给系统调用的参数不正确。

9.recv(经socket接收数据)  相关函数  recvfrom,recvmsg,send,sendto,socket

表头文件 

#include<sys/types.h>

#include<sys/socket.h>

定义函数 

int recv(int s,void *buf,int len,unsigned int flags);

函数说明 

recv()用来接收远端主机经指定的socket传来的数据,并把数据存到由参数buf 指向的内存空间,参数len为可接收数据的最大长度。

参数

flags一般设0。其他数值定义如下:

MSG_OOB 接收以out-of-band 送出的数据。

MSG_PEEK 返回来的数据并不会在系统内删除,如果再调用recv()会返回相同的数据内容。

MSG_WAITALL强迫接收到len大小的数据后才能返回,除非有错误或信号产生。

MSG_NOSIGNAL此操作不愿被SIGPIPE信号中断返回值成功则返回接收到的字符数,失败返回-1,错误原因存于errno中。

错误代码

EBADF 参数s非合法的socket处理代码

EFAULT 参数中有一指针指向无法存取的内存空间

ENOTSOCK 参数s为一文件描述词,非socket。

EINTR 被信号所中断

EAGAIN 此动作会令进程阻断,但参数s的socket为不可阻断

ENOBUFS 系统的缓冲内存不足。

ENOMEM 核心内存不足

EINVAL 传给系统调用的参数不正确。

10.read(由已打开的文件读取数据)  相关函数  readdir,write,fcntl,close,lseek,readlink,fread

表头文件 

#include<unistd.h>

定义函数 

ssize_t read(int fd,void * buf ,size_t count);

函数说明 

read()会把参数fd 所指的文件传送count个字节到buf指针所指的内存中。若参数count为0,则read()不会有作用并返回0。返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。

附加说明  如果顺利read()会返回实际读到的字节数,最好能将返回值与参数count 作比较,若返回的字节数比要求读取的字节数少,则有可能读到了文件尾、从管道(pipe)或终端机读取,或者是read()被信号中断了读取动作。当有错 误发生时则返回-1,错误代码存入errno中,而文件读写位置则无法预期。

错误代码 

EINTR 此调用被信号所中断。

EAGAIN 当使用不可阻断I/O 时(O_NONBLOCK),若无数据可读取则返回此值。

EBADF 参数fd 非有效的文件描述词,或该文件已关闭。

11.write(将数据写入已打开的文件内)  相关函数  open,read,fcntl,close,lseek,sync,fsync,fwrite

表头文件 

#include<unistd.h>

定义函数 

ssize_t write (int fd,const void * buf,size_t count);

函数说明 

write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。当然,文件读写位置也会随之移动。

返回值 

如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中。

错误代码 

EINTR 此调用被信号所中断。

EAGAIN 当使用不可阻断I/O 时(O_NONBLOCK),若无数据可读取则返回此值。

EADF 参数fd非有效的文件描述词,或该文件已关闭。

补充:文件描述符

系统提供了4个宏对描述符集进行操作:

#include <sys/select.h>

#include <sys/time.h>

void FD_SET(int fd, fd_set *fdset);

void FD_CLR(int fd, fd_set *fdset);

void FD_ISSET(int fd, fd_set *fdset);

void FD_ZERO(fd_set *fdset);

宏FD_SET 设置文件描述符集fdset中对应于文件描述符fd的位(设置为1),宏FD_CLR清除文件描述符集fdset中对应于文件描述符fd的位(设置为 0),宏FD_ZERO清除文件描述符集fdset中的所有位(既把所有位都设置为0)。使用这3个宏在调用select前设置描述符屏蔽位,在调用 select后使用FD_ISSET来检测文件描述符集fdset中对应于文件描述符fd的位是否被设置。

过去,描述符集被作为一个整数位屏蔽码得到实现,但是这种实现对于多于32个的文件描述符将无法工作。描述符集现在通常用整数数组中的位域表示,数 组元素的每一位对应一个文件描述符。例如,一个整数占32位,那么整数数组的第一个元素代表文件描述符0到31,数组的第二个元素代表文件描述符32到 63,以此类推。宏FD_SET设置整数数组中对应于fd文件描述符的位为1,宏FD_CLR设置整数数组中对应于fd文件描述符的位为0,宏 FD_ZERO设置整数数组中的所有位都为0。假设执行如下程序后:

#include <sys/select.h>

#include <sys/time.h>

fd_set readset;

FD_ZERO(&readset);

FD_SET(5, &readset);

FD_SET(33, &readset);

 

再执行如下程序后:

FD_CLR(5, &readset);

 

通常,操作系统通过宏FD_SETSIZE来声明在一个进程中select所能操作的文件描述符的最大数目。例如:

在4.4BSD的头文件中我们可以看到:

#ifndef FD_SETSIZE

#define FD_SETSIZE 1024

#endif

在红帽Linux的头文件<bits/types.h>中我们可以看到:

#define __FD_SETSIZE 1024

以及在头文件<sys/select.h>中我们可以看到:

#include <bits/types.h>

#define FD_SETSIZE __FD_SETSIZE

既定义FD_SETSIZE为1024,一个整数占4个字节,既32位,那么就是用包含32个元素的整数数组来表示文件描述符集。我们可以在头文件 中修改这个值来改变select使用的文件描述符集的大小,但是必须重新编译内核才能使修改后的值有效。当前版本的unix操作系统没有限制 FD_SETSIZE 的最大值,通常只受内存以及系统管理上的限制。

我们明白了文件描述符集的实现机制之后,就可对其进行灵活运用。(以下程序在红帽Linux 6.0下运行通过,函数fd_isempty用于判断文件描述符集是否为空;函数fd_fetch取出文件描述符集中的所有文件描述符)

#include <stdio.h>

#include <string.h>

#include <sys/time.h>

#include <sys/select.h>

struct my_fd_set{

fd_set fs; //定义文件描述符集fs

unsigned int nconnect; //文件描述符集fs中文件描述符的个数

unsigned int nmaxfd; //文件描述符集fs中最大的文件描述符

};

/* 函数fd_isempty用于判断文件描述符集是否为空,为空返回1,不为空则返回0 */

int fd_isempty(struct my_fd_set *pfs)

{

int i;

/* 文件描述符集fd_set是通过整数数组来实现的,所以定义整数数组myset的元素个数为文件描述符集fd_set所占内存空间的字节数除以整数所占内存空间的字节数。

*/

unsigned int myset[sizeof(fd_set) / sizeof(int)];

/* 把文件描述符集pfs->fs 拷贝到数组myset */

memcpy(myset, &pfs->fs, sizeof(fd_set));

for(i = 0; i < sizeof(fd_set) / sizeof(int); i++)

/* 如果myset的某个元素不为0,说明文件描述符集不为空,则函数返回0 */

if (myset[i])

return 0;

return 1; /* 如果myset的所有元素都为0,说明文件描述符集为空,则函数返回1 */

}

/* 函数fd_fetch对文件描述符集进行位操作,把为1的位换算成相应的文件描述符,然后就可对其进行I/O操作 */

void fd_fetch(struct my_fd_set *pfs)

{

struct my_fd_set *tempset; //定义一个临时的结构指针

unsigned int myset[sizeof(fd_set)/sizeof(unsigned int)];

unsigned int i, nbit, nfind, ntemp;

tempset = pfs;

memcpy(myset, &tempset->fs, sizeof(fd_set));

/* 把最大的文件描述符maxfd除以整数所占的位数,得出maxfd在文件描述符集中相应的位对应于整数数组myset的相应元素的下标,目的是为了减少检索的次数 */

nfind = tempset->nmaxfd / (sizeof(int)*8);

for (i = 0; i <= nfind; i++) {

/* 如果数组myset的某个元素为0,说明这个元素所对应的文件描述符集的32位全为0,则继续判断下一元素。*/

if (myset[i] == 0) continue;

/* 如果数组myset的某个元素不为0,说明这个元素所对应的文件描述符集的32位中有为1的,把myset[i]赋值给临时变量ntemp,对ntemp进行位运算,把为1的位换算成相应的文件描述符 */

ntemp = myset[i];

/* nbit记录整数的二进制位数,对ntemp从低到高位进行&1运算,直到整数的最高位,或直到文件描述符集中文件描述符的个数等于0 */

for (nbit = 0; tempset->nconnect && (nbit < sizeof(int)*8); nbit++) {

if (ntemp & 1) {

/* 如果某位为1,则可得到对应的文件描述符为nbit + 32*I,然后我们可对其进行I/O操作。这里我只是做了简单的显示。*/

printf(“i = %d, nbit = %d, The file description is %dn”, i, nbit, nbit + 32*i);

/* 取出一个文件描述符后,将文件描述符集中文件描述符的个数减1 */

tempset->nconnect–; }

ntemp >>= 1; // ntemp右移一位

}

}

}

 

/* 下面的主程序是对以上两个函数的测试 */

main()

{

/* 假设fd1,fd2,fd3为3个文件描述符,实际运用中可为Socket描述符等 */

int fd1 = 7, fd2 = 256, fd3 = 1023, isempty;

struct my_fd_set connect_set;

connect_set.nconnect = 0;

connect_set.nmaxfd = 0;

FD_ZERO(&connect_set.fs);

/* FD_SET操作前对函数fd_isempty进行测试 */

isempty = fd_isempty(&connect_set);

printf(“isempty = %dn”, isempty);

FD_SET(fd1, &connect_set.fs);

FD_SET(fd2, &connect_set.fs);

FD_SET(fd3, &connect_set.fs);

connect_set.nconnect = 3;

connect_set.nmaxfd = fd3 ;

/* FD_SET操作后,既把文件描述符加入到文件描述符集之后,对函数fd_isempty进行测试 */

isempty = fd_isempty(&connect_set);

printf(“isempty = %dn”, isempty);

/* 对函数fd_ fetch进行测试 */

fd_fetch(&connect_set);

}

 

/* 程序输出结果为 :*/

isempty is 1

isempty is 0

i = 0, nbit = 7, The file description is 7

i = 8, nbit = 0, The file description is 256

i = 31, nbit = 31, The file description is 1023

本文固定链接: http://www.ccsbbs.com.cn/archives/6792.html | 极限手指

该日志由 极限手指 于2013年05月30日发表在 C语言 分类下, 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
原创文章转载请注明: C语言之网络编程(SOCKET) | 极限手指

C语言之网络编程(SOCKET):等您坐沙发呢!

发表评论

您必须 [ 登录 ] 才能发表留言!