学习笔记之SOCKET网络编程之二

来源:互联网 发布:数学建模含金量 知乎 编辑:程序博客网 时间:2024/05/17 07:54
1.sendto()和recvfrom()函数
Sendto()和recvfrom()函数用于在无连接的数据报套接字方式下进行数据发送和接收.

sendto()函数
定义:int sendto(int s,void *msg,int len,unsigned int flags,struct sockaddr *to, int tolen);
在发送数据时.由于本地端并没有与远程机器建立一个连接.所以在用sendto()发送数据时我们要告诉它把数据发送到哪里?也就是告诉sendto()发送数据的目的地.
所以它比send()函数多了两个参数.
参数 "struct sockaddr* to"用来告诉它发送数据目的地.包括目标机器的IP和端口等信息.
参数 "int tolen"是struct sockaddr结构的大小.
除此之外其它的参数与send()函数完全相同.
函数成功返回实际发送数据的长度.失败返回-1.

recvfrom()函数
定义:int recvfrom(int s,void *buf,int len,unsigned int flags,sockaddr*from,int *fromlen);
因为是利用数据报套接字传送的数据.所以并没有连接.所以recvfrom()在收到数据后需要知道数据到底是谁发来的?在这里称为"源".所以它比recv()函数多了两个参数.
参数from用来记录源机器的地址信息.包括IP地址和端口等信息.
参数fromlen同样是struct sockaddr结构的大小.
其它参数与recv()函数完全相同.
函数成功时返回实际收到的数据长度.失败返回-1.

2.套接字的关闭.
当我们完成通讯后我们需要把套接字关闭.从而释放资源.
关闭套接字的操作通过调用close()函数来实现.
不管是有连接的流式套接字还是没有连接的数据包式的套接字.只要任何一端调用了close()函数关闭了套接字.都将无法再进行通讯.
另外还有一个函数就是shutdown().它允许你将一定方向上的通讯关闭.当然也可双向通讯都关闭.
也就是说shutdown()函数可以让某一端只接收数据.或者只发送数据.
定义:int shutdown(int s,int how);
参数s是我们将要操作的套接字.
参数how是具体的操作方式.
A.为0时表示不允许接收数据
B.为1时表示不允许发送数据
C.为2时表示不允许接收和发送数据(双向通讯都将关闭,与调用close()函数效果一样.)
函数成功时返回0失败时返回-1.

3.其它.
getpeername()函数.
功能:它用来在有连接的流式套接字中获取对方的地址信息.(包括IP地址和端口)
定义:int getpeername(int s, struct sockaddr *addr, int *addrlen);
参数addr用来存放获取的地址信息.
函数失败返回-1.

gethostname()函数.
功能:它用来获取本机的名字.
定义:int gethostname(char *hostname, size_t len);
参数hostname是一个字符类型的缓冲区.它用来保存本机名字
参数len是缓冲区的长度.
函数成功时返回0.失败时返回-1.

gethostbyname(char*name)函数.
功能:返回由参数name指定的机器的地址信息
定义:struct hostent *gethostbyname(const char *name);
参数name可以是用gethostname()函数获取的机器名字
函数成功返回一个指向struct hostent结构体的指针.失败返回NULL

另:此函数返回的是一个struct hostent结构指针.该结构就包含了一个机器的地址信息
该结构的定义:
struct hostent
{
   char *h_name;                   //地址的正式名称
   char **h_aliases;               //空字节 地址的预备名称的指针
   int h_addrtype;                 //地址的类型.WINDOWS中通常为AF_INET
   int h_length;                   //地址的长度.以位来计算(不是以字节来计算)
   char **h_addr_list;             //零字节 主机网络地址指针.(网络字节顺序)
#define h_addr  h_addr_list[0]       //h_addr_list中的第一地址
};


4.关于阻塞与非阻塞.

阻塞.
简单的说阻塞就是当你调用accept(),send()或者是recv()等过程后.程序必须要等待一个返回结果程序才会继续执行下去.在这种情况下,如果你的程序是单线程的.那么你的程序就会完全挂起.

非阻塞.
而非阻塞正好相反.非阻塞套接字是指执行此套接字的网络调用时,不管是否执行成功,都立即返回.

习惯上也称之为同步阻塞和异步非阻塞.


在默认情况下套接字都是阻塞模式的.如果要改变这个设置就要调用fcntl()函数.

fcntl()定义如下:
#include <fcntl.h>
int   fcntl(int  s,int  cmd,  .../* int arg */);
参数s是我们将要设置的套接字.
参数cmd有多种形式.在这里一般设置为F_SETFL.
第三个参数总是一个整数.在此处只需要设置成O_NONBLOCK(表示非阻塞模式)

注意:在windows下的socket编程中.是用iooctlsocket()函数来实现fcntl()函数的功能的.
它的定义如下:
int ioctlsocket(int s,long cmd,u_long *argp);
功能:控制套接字的模式.
参数s是将要操作的套接字.
参数cmd是对套接字的操作方式.
参数argp指向cmd命令所带参数的指针.

参数cmd支持下列几种命令:
A.FIONBIO允许或禁止套接字s的非阻塞模式.参数argp指向一个无符号长整型.如果要允许非阻塞模式则将参数argp设成非零.如果要禁止非阻塞模式(也就是阻塞模式)则将参数argp设成零即可.
注:一个套接字进行了WSAAsyncSelect()操作.它是套接字的一种模式,它将套接字自动设置成非阻塞模式.则任何用试图ioctlsocket()来重新设置套接成阻塞模式的操作都将失败.必须先调用WSAAsyncSelect()来禁止WSAAsyncSelect后才可以.关于WSAAsyncSelect()请查阅其它资料.
B.FIONREAD确定套接字s自动读入数据量.参数argp此时用来存放ioctlsocket()的返回值.如果套接字是SOCKET_STREAM类型(即流式套接字)则FIONREAD返回一次在recv()中所接收到的所有数据量.(即数据长度).这通常与套接字中排队的数据总量相同.如果套接字是SOCK_DGRAM类型.则FIONREAD返回套接字上排队的第一个数据报的大小.
还有一些其它的命令.略.

函数调用成功返回0.失败返回-1.

从系统性能上看,用非阻塞的socket效率和性能更高,但是编程更复杂,特别是当你使用事件或者消息的时候,但是,你可以通过多个工作线程管理多个socket连接,效率非常高,不需要每个工作线程只管理一个socket连接. 用阻塞的方式比较简单,但当较多客户端时消耗系统资源太多.
其大概思想是建立一个线程池,当有socket的事件需要处理时,从线程池中取一个线程来执行,执行完,将线程归还到线程池中.
每个socket连接的时间较长,不断的与服务器交互.每个连接的socket并不是每时每刻都在收发数据.收发数据是断断续续的.这种情况就很适合于上面的方法.

简单的讲同步阻塞常用于连接较少流量较大的地方.非阻塞则刚好相反.用于连接较多.流量较小的地方.
如果要采用阻塞模式.并且要同时处理多个连接.就只能运用多线程来处理.

多路同步.
假设有一台服务器.它一边要进行监听是否有连接到来,还要在其它已经完成的连接上接收数据.也就是accept()和recv()两个函数的调用.当你在调用accept()时遇到阻塞该怎么办.前提是你并没有调用
fcntl()函数把套接字设置非阻塞模式.这时你就可以调用select().它让你可以同时监视多个套接字.它可以告诉你.哪个套接字准备读,哪个套接字准写,哪个套接字发生了意外.

select()函数的定义如下:
int select(int nums,fd_set*reads, fd_set*writes,fd_set *excepts,timeval *timeout);

在讲解select()函数之前我们先讲一下fd_set.
它表示一个集合.具体的讲是一个文件描述符的集合.在此例中都把它理解为套接字描述符.
在Unix中一切都是文件.包括输入设备键盘.硬盘上的文件.还有专门用于网络传输的socket(套接字)等等.所以.在这里都把它当作是socket套接字.
那么文件描述符或者是套接字描述符就好比是WINDOWS下面的句柄.

fd_set的定义如下:
struct fd_set
{
        u_int   fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
};

对这些集合的操作都是通过一些特定的宏来完成的.
    FD_ZERO(fd_set *set)          //清除一个文件描述符集合
      FD_SET(int fd, fd_set *set)   //把fd添加到集合set中
      FD_CLR(int fd, fd_set *set)   //从集合set中移去fd
      FD_ISSET(int fd, fd_set *set) //测试fd是否在集合set中

现在再来说说select()函数,以及它的用法.
该函数的功能就是用来等待套接字(这里只讨论套接字)状态的改变.
参数nums代表最大的套接字描述符加1.
参数reads,writes和excepts是套接字描述符集合,用来回传该套接字描述符的读,写或例外的状况.
参数timeout指向一个timeval结构.它用来设定select等待的时间.可以设置到微秒级.1秒等于1000毫秒.1毫秒等于1000微秒.虽然可以精确到1微秒.但这只是理想的.实际上跟系统还有硬件有关.
timeval结构的定义如下:
struct timeval
{
   int tv_sec;   /* 秒 */
   int tv_usec;  /* 微秒 */
};

当然你可以将参数timeout设置成NULL,那么它将永远不会超时.
成功返回套接字描述状态改变的个数,如果返回0表示在套接字描述符状态改变前已经超过参数timeout设定的时间.当有错误发生时则返回-1.此时参数reads,writes,execepts和timeout的值变得不可预测.
同时有错误发生时,它会设置全局错误变量.常见错误有下面几种.

EBADF 套接字描述符无效或该套接字已关闭
EINTR 此调用被信号所中断
EINVAL 参数n 为负值
ENOMEM 核心内存不足

select()函数的常见用法.

//============
    ......
sockfd = socket(AF_INET, SOCK_STREAM, 0); //获取一个套接字
         ......
fs_set readset; //定义套接字集合,用于查看读状态
FD_ZERO(&readset); //对该集合进行清除操作.
timeval  out; //创建一个timeval结构
out.tv_sec=5; //设定等待秒数为5秒
out.tv_usec=1000*500; //设定等毫秒数为500毫秒
//总的等待时间为5秒+500毫秒.
select(sockfd+1,&readset,NULL,NULL,&out); //select()函数的调用.
if(FD_ISSET(sockfd,readset))//进行判断
    ......
//=============



5.socket编程的最后一个问题
这也是socket编程中的第一个问题.如果你是在windows下面编程的话.

在windows下面进行socket编程我们首先要对socket进行初始化.这时就要用到WSAStartup()函数.
在windows中要调用所有的socket函数之前.我们必须先调用WSAStartup()函数
其定义如下:
int WSAStartup(WORD wVer,WSADATA* lpWSAData);
参数wVer指明可使用的windows socket的版本号.高位字节标明副版本号.低位字字指明主版本号.
参数lpWSAData是指向WSADATA结构的指针.用来接收windows socket实现的细节.
成功时返回0失败返回错误代码.

这里将用MAKEWORD()宏.

MAKEWORD(a,b)它的作用是把a和b组成一个WORD值.
其中第一个参数a是高8位的值.第二个参数b是低8位的值.
我们就是用它来把windows socket的版本号放入WSAStartup()函数的.

例程:
//=====================
WORD sver=MAKEWORD(1,1);//设置socket版本号为1.1
WSADATA wsda;//创建WSADATA结构
if(WSAStartup(sver,&wsda)==0)//调用WSAStartup函数初始化socket
{
    //调用成功.初始化成功
}
//=====================

另外一个函数.它是windows socket编程中的最后一个问题.
WSACleanup()函数.它是终止windows socket的调用.注销windows socket.释放资源.
任何打开的并已建立连接的SOCK_STREAM流式套接字.在调用WSACleanup()时会重置.而已经由close()关闭却仍有要发送而未发送的数据的套接字则不会受影响.该数据仍要发送.
通常.WSACleanup()函数与WSAStartup()函数应成对出现.也就是说对于一个进程的每一次WSAStartup()调用.必须有一个WSACleanup()调用.
但是只有最后一个WSACleanup()调用才会做实际的清除工作.前面的WSACleanup()调用仅仅将windows socket DLL中的内置引用计数递减1.
一般情况下,为了确保WSACleanup()调用了足够的次数.可以在最后用一个循环来不断的调用WSACleanup()函数.直到返回WSANOTINITIALISED为止.
该函数的定义:
int WSACleanup ( void );
函数成功返回0.失败返回-1.同时会设置全局错误变量.
常见的几种错误代码.
A.WSANOTINITIALISED使用本函数之前没有一次成功的WSAStartup()调用.简单的讲.它就是说.前面已经没有WSAStartup()调用了.在这里可以不需要再调用WSACleanup()函数来释放资源了.
B.WSAEINPROGRESS一个阻塞的windows socket操作正在进行.简单的讲.它就是说你正准备注销的这个套接字中某一个操作(比如recv())遇到了阻塞.


<全文完>


 来自:http://blog.sina.com.cn/s/blog_56ea069101000bxm.html
原创粉丝点击