socket 由浅入深 系列函数(二)

来源;MSDN 网络整理

主要介绍:CreateCompletionPort, socket、bind、listen、connect、accept、send、sendto、recv、recvfrom、close、shutdown

原理可看:

CreateCompletionPort函数

1. 用于创建一个完成端口对象。

2. 将一个句柄同完成端口关联到一起。

HANDLE CreateIoCompletionPort(
    HANDLE FileHandle,
    HANDLE ExistingCompletionPort,
    ULONG_PTR CompletionKey,
    DWORD NumberOfConcurrentThreads
);

最开始创建一个完成端口时,唯一感兴趣的参数便是NumberOfConcurrentThreads(并发线程的数量);前面三个参数都会被忽略。NumberOfConcurrentThreads参数的特殊之处在于,它定义了在一个完成端口上,同时允许执行的线程数量。理想情况下,我们希望每个处理器各自负责一个线程的运行,为完成端口提供服务,避免过于频繁的线程“场景”切换。若将该参数设为0,表明系统内安装了多少个处理器,便允许同时运行多少个线程!可用下述代码创建一个I/O完成端口:

msdn demo(也可参考:《Networking Programming for Microsoft Windows》第八章)

HANDLE    hIocp;

hIocp = CreateIoCompletionPort(
    INVALID_HANDLE_VALUE,
    NULL,
    (ULONG_PTR)0,
    0);
if (hIocp == NULL) {
    // Error
}

intsocket(int domain,int type, int protocol)

_________________________返回值:非负描述符 – 成功,-1 - 出错

其中:

family指明了协议族/域,通常AF_INET、AF_INET6、AF_LOCAL等;

type是套接口类型,主要SOCK_STREAM、SOCK_DGRAM、SOCK_RAW;

protocol一般取为0。成功时,返回一个小的非负整数值,与文件描述符类似。

对于windows环境下,在调用该函数之前需首先调用WSAStartup函数完成对Winsock服务的初始化,如

#include<WinSock2.h>

WSADATA wdata;

if ( WSAStartup(MAKEWORD(2,2), &wdata) !=0 ){

return INVALID_SOCKET;

}

后面即可调用socket函数,参数意义与linux环境一致。

intbind(int sockfd,const struct sockaddr* myaddr,socklen_t addrlen)

_________________________返回值:0 – 成功,-1 - 出错

当socket函数返回一个描述符时,只是存在于其协议族的空间中,并没有分配一个具体的协议地址(这里指IPv4/IPv6和端口号的组合),bind函数可以将一组固定的地址绑定到sockfd上。

其中:

sockfd是socket函数返回的描述符;

myaddr指定了想要绑定的IP和端口号,均要使用网络字节序-即大端模式;

addrlen是前面struct sockaddr(与sockaddr_in等价)的长度。

为了统一地址结构的表示方法,统一接口函数,使得不同的地址结构可以被bind()、connect()、recvfrom()、sendto()等函数调用。但一般的编程中并不直接对此数据结构进行操作,而使用另一个与之等价的数据结构sockaddr_in。

通常服务器在启动的时候都会绑定一个众所周知的协议地址,用于提供服务,客户就可以通过它来接连服务器;而客户端可以指定IP或端口也可以都不指定,未分配则系统自动分配。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

Windows下的版本:

Int bind( IN SOCKET s, IN const struct sockaddr FAR * name, IN int namelen);

listen

intlisten(int sockfd,int backlog)

______________________返回值:0 – 成功,-1 - 出错

(截图来自:《UNIX网络编程第一卷》)

两个队列之和数量不得超过backlog.

 connect

intconnect(int sockfd,conststruct sockaddr *addr, socklen_t addrlen)

______________________返回值:0 – 成功,-1 - 出错

通过此函数建立于TCP服务器的连接,实际是发起三次握手过程,仅在连接成功或失败后返回。参数sockfd是本地描述符,addr为服务器地址,addrlen是socket地址长度。

UDP的connect函数,结果与tcp调用不相同,没有三次握手过程。内核只是记录对方的ip和端口号,他们包含在传递给connect的套接口地址结构中,并立即返回给调用进程。

  accept

intaccept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)

______________________返回值:非负描述符 – 成功,-1 - 出错

(截图来自:《UNIX网络编程第一卷》)

  send

ssize_tsend(int sockfd,constvoid *buf, size_t len,int flags)

返回值:

>0 – 成功拷贝至发送缓冲区的字节数(可能小于len),

-1 – 出错,并置错误号errno.

Windows版本:

int send(IN SOCKET s, IN const char FAR * buf, IN int len, IN int flags)

失败时返回 -1/SOCKET_ERROR

其中:

sockfd:发送端套接字描述符(非监听描述符)

buf:应用要发送数据的缓存

len:实际要发送的数据长度

flag:一般设置为0

每个TCP套接口都有一个发送缓冲区,它的大小可以用SO_SNDBUF这个选项来改变。调用send函数的过程,实际是内核将用户数据拷贝至TCP套接口的发送缓冲区的过程:若len大于发送缓冲区大小,则返回-1;否则,查看缓冲区剩余空间是否容纳得下要发送的len长度,若不够,则拷贝一部分,并返回拷贝长度(指的是非阻塞send,若为阻塞send,则一定等待所有数据拷贝至缓冲区才返回,因此阻塞send返回值必定与len相等);若缓冲区满,则等待发送,有剩余空间后拷贝至缓冲区;若在拷贝过程出现错误,则返回-1。关于错误的原因,查看errno的值。

如果send在等待协议发送数据时出现网络断开的情况,则会返回-1。注意:send成功返回并不代表对方已接收到数据,如果后续的协议传输过程中出现网络错误,下一个send便会返回-1发送错误。TCP给对方的数据必须在对方给予确认时,方可删除发送缓冲区的数据。否则,会一直缓存在缓冲区直至发送成功(TCP可靠数据传输决定的)。

  sendto

ssize_t sendto(int sockfd,const void *buf, size_t len, int flags,

const struct sockaddr *dst_addr, socklen_t addrlen);

windows版本:

int sendto(IN SOCKET s, IN const char FAR * buf, IN int len, IN int flags, IN const struct sockaddr FAR * to, IN int tolen)

通常用于UDP套接口,用数据报方式进行数据传输。由于无连接的数据报模式下,没有建立连接,需指明目的地址,addrlen通常为sizeof(sockaddr)的长度。成功时返回发送的字节数,失败返回-1。

当本地与不同目的地址通信时,只需指定目的地址,可使用同一个UDP套接口描述符sockfd,而TCP要预先建立连接,每个连接都会产生不同的套接口描述符,体现在:客户端要使用不同的fd进行connect,服务端每次accept产生不同的fd。

因为UDP没有真正的发送缓冲区,因为是不可靠连接,不必保存应用进程的数据拷贝,应用进程中的数据在沿协议栈向下传递时,以某种形式拷贝到内核缓冲区,当数据链路层把数据传出后就把内核缓冲区中数据拷贝删除。因此它不需要一个发送缓冲区。写UDP套接口的sendto/write返回表示应用程序的数据或数据分片已经进入链路层的输出队列,如果输出队列没有足够的空间存放数据,将返回错误ENOBUFS.

关于TCP/UDP套接口的发送缓冲区理解:

(1)下图展示了应用进程写数据到TCP套接口的过程:

每一个TCP套接口有一个发送缓冲区,我们可以用SO_SNDBUF套接口选项来改变这个缓冲区的大小。当应用程序调用write时,内核从应用程序进程的缓冲区中拷贝所有数据到套接口的发送缓冲区。如果套接口的发送缓冲区容不下应用程序的所有数据(或是应用程序的缓冲区大于套接口发送缓冲区,或是套接口发送缓冲区还有其他数据),应用进程将被挂起(睡眠)。这里假设套接口是阻塞的,它是通常的缺省设置(还有非阻塞的套接口)。内核将不从write系统调用返回,直到应用程序缓冲区中的所有数据都拷贝到套接口发送缓冲区。因此从写一个TCP套接口的write调用成功返回仅仅表示我们可以重新使用应用进程的缓冲区。它并不告诉我们对端的TCP或应用程序已接收到数据。

TCP取套接口发送缓冲区的数据并把它发送给对端TCP,其过程基于TCP数据传送的所有规则。对端TCP必需确认收到数据,只有收到对端的ACK,本端TCP才能删除套接口发送缓冲区中已确认的数据。TCP必需保留数据拷贝直到对端确认为止。

TCP以MSS大小的或更小的块把数据传递给IP,同时给每个数据块安上一个TCP头部以构成TCP分节,其中的MSS是由对端通告的,当对端未通告时就用536这个值(IPv4的最小重组缓冲区字节数576减去IPv4头部字节20和TCP头部字节数20)。IP给每个TCP分节安上IP头部以构成IP数据报,查找其宿IP地址的路由表项以确定外出接口,然后把数据报传递给相应的数据链路。IP可能在把数据报传递给数据链路之前将其分片,不过我们已经谈到MSS选项的目的之一就是试图避免分片,而较新的实现又使用了路径MTU发现功能。每个数据链路都有一个输出队列,如果该队列已满,那么新到的分组将被丢弃,并沿协议栈向上返回一个错误,从链路层到IP层,再从IP层到TCP层。TCP将注意到这个错误,并在以后某个时刻重传相应分片。应用进程并不知道这种暂时情况。

(2)下图展示了应用进程写数据到UDP套接口的过程:

这一次我们展示的套接口发送缓冲区用虚线框,因为它并不存在。UDP套接口有发送缓冲区大小(我们可以用SO_SNDBUF套接口选项修改),不过它仅仅是写到套接口的UDP数据报的大小上限。如果应用进程写一个大于套接口发送缓冲区大小的数据包,内核将返回一个EMSGSIZE错误。既然UDP是不可靠的,它不必保存应用程序的数据拷贝,因此无需一个真正的发送缓冲区。(应用进程的数据在沿协议向下传递时,以某种形式拷贝到内核的缓冲区,然而数据链路层在送出这些数据后将丢弃该拷贝)

UDP简单地给用户数据报安上它的8个字节的头部以构成UDP数据报,然后传递给IP。IPv4或IPv6给UDP数据报安上相应的IP头部以构成IP数据报,执行路由操作确定外出接口,然后直接把数据包加入数据链路层输出队列(如果适合于MTU),或者分片后再把每个片加入数据链路层的输出队列。如果某个UDP应用进程发送大数据报,那么它比TCP应用进程更有可能分片,因为TCP会把应用数据划分成MSS大小的块,而UDP却没有对等的手段。

从写UDP套接口的write调用成功地返回表示用户写入的数据报或其所有片段已被加入数据链路层的输出队列。如果该队列没有足够的空间存放该数据报或它的某个片段,内核通常将给应用程序返回一个ENOBUFS错误。

  recv

ssize_t recv(int sockfd,void *buf, size_t len,int flags)

其中:

sockfd:接收端套接字描述符;

buf:指定缓冲区地址,用于存储接收数据;

len:指定的用于接收数据的缓冲区长度;

flags:一般指定为0

表示从接收缓冲区拷贝数据。成功时,返回拷贝的字节数,失败返回-1。阻塞模式下,recv/recvfrom将会阻塞到缓冲区里至少有一个字节(TCP)/至少有一个完整的UDP数据报才返回,没有数据时处于休眠状态。若非阻塞,则立即返回,有数据则返回拷贝的数据大小,否则返回错误-1,置错误码为EWOULDBLOCK。

  recvfrom

ssize_t recvfrom(int sockfd,void *buf, size_t len, int flags,

struct sockaddr *src_addr, socklen_t *addrlen)

windows版本:

int recvfrom(IN SOCKET s, OUT char FAR * buf, IN int len, IN int flags, OUT struct sockaddr FAR * from,IN OUTint FAR * fromlen)

着重强调参数:

sockfd:接收端套接字描述

buf:用于接收数据的应用缓冲区地址

len:指名缓冲区大小

flags:通常为0

src_addr:数据来源端的地址

addrlen:src_addr地址的长度

注意后两个参数是输出参数,其中addrlen既是输入又是输出参数,即值-结果参数,需要在调用时,指明src_addr的长度。另外,如果不关心数据发送端的地址,可以将后两者均设置为NULL。

 close

close缺省功能是将套接字作“已关闭”标记,并立即返回到调用进程,该套接字描述符不能再为该进程所用:即不能作为read和write(send和recv)的参数,但是TCP将试着发送发送缓冲区内已排队待发的数据,然后按正常的TCP连接终止序列进行操作(断开连接4次握手-以FIN为首的4个TCP分节)。

 shutdown

shutdown不仅可以灵活控制关闭连接的读、写或读写功能,而且会立即执行相应的断开动作(发送终止连接的FIN分节等),此时不论有多少进程共享此套接字描述符,都将不能再进行收发数据。

时间: 2024-10-02 08:06:54

socket 由浅入深 系列函数(二)的相关文章

socket 由浅入深 系列函数(五)socket 阻塞 与 非阻塞

socket 阻塞 与 非阻塞 author:songyanwu 1 概念理解 其实在我们在进行网络编程,在理解阻塞与非阻塞同时,还应该清楚同步与异步! 同步与阻塞 同步和阻塞是不同的,可是有时候在理解有会很模糊: 同步:事情一件件的做,做完一件返回一件,做不完不回复也不返回. 阻塞;  阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行).函数只有在得到结果之后才会返回. 说明:同步和阻塞 在以后接触内核驱动 的时候会

socket 由浅入深系列------ 原理(一)

来自:网络整理 个人觉得写一个网络应用程序没有是一件非常easy的事.其实,我们刚開始的时候总觉得的原则: 建立------>连接套接字------->接受一个连接---->发送数据 而真正复杂编写一个网络应用程序的规模从一个连接到成千上万的连接! 那么本系列将对sockt由浅入深的介绍. msdn The overlapped I/O mechanism in Win32? allows an application to initiate an operation and recei

php中关于socket的系列函数总结

php中关于socket的系列函数总结 本文列举了所有关于PHP语言中使用socket相关服务的一些函数.注意使用如下函数之前,你需要确保你的socket已打开,如果你没有打开,请编辑你的php.ini文件,去掉下面这行前面的注释(分号): extension=php_sockets.dll 如果你无法去掉注释,那么请使用下面的代码来加载扩展库: <?php if(!extension_loaded('sockets')){  if(strtoupper(substr(PHP_OS,3))==&qu

Winsock系列函数 及 Socket通信流程

Socket是一种网络通信机制 Winsock系列函数 1. Socket 创建socket 2. Connect 尝试连接远端Socket 3. Send 在某个Socket 向远端发送数据 4. Recv 接收远端数据 5. Closesocket 关闭连接 6. Listen 在某个Socket上建立监听 7. Accept 接收一条新的连接 (是接收连接而不是接收数据) 8. Bind 给一个Socket分配一个本地协议地址 9. Select 用于检测Socket状态,用于高级通信 1

算法系列之二十三:离散傅立叶变换之音频播放与频谱显示

算法系列之二十三:离散傅立叶变换之音频播放与频谱显示 算法系列之二十三离散傅立叶变换之音频播放与频谱显示 导语 什么是频谱 1 频谱的原理 2 频谱的选择 3 频谱的计算 显示动态频谱 1 实现方法 2 杂项说明 结果展示 导语 频谱和均衡器,几乎是媒体播放程序的必备物件,没有这两个功能的媒体播放程序会被认为不够专业,现在主流的播放器都具备这两个功能,foobar 2000的十八段均衡器就曾经让很多人着迷.在上一篇对离散傅立叶变换介绍的基础上,本篇就进一步介绍一下频谱是怎么回事儿,下一篇继续介绍

Tokyo Tyrant(TTServer)系列(二)-启动参数和配置

启动参数介绍 ttserver命令可以启动一个数据库实例.因为数据库已经实现了Tokyo Cabinet的抽象API,所以可以在启动的时候指定数据库的配置类型. 支持的数据库类型有: 内存hash数据库 内存tree数据库 hash数据库 B+ tree数据库, 命令通过下面的格式来使用,'dbname'制定数据库名,如果省略,则被视作内存hash数据库. ttserver [-host name] [-port num] [-thnum num] [-tout num] [-dmn] [-pi

C#微信公众号开发系列教程二(新手接入指南)

http://www.cnblogs.com/zskbll/p/4093954.html 此系列前面已经更新了两篇博文了,都是微信开发的前期准备工作,现在切入正题,本篇讲解新手接入的步骤与方法,大神可直接跳过,也欢迎大神吐槽. 目录 C#微信公众号开发系列教程一(调试环境部署) C#微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) 微信公众平台消息接口的工作原理大概可以这样理解:从用户端到公众号端一个流程是这样的,用户发送消息到微信服务器,微

JSqlParser系列之二代码结构(原)

JSqlParser系列之二代码结构(原) 博客园 百味木屋原创,转载请注明出处. 上一篇文章简单介绍如何建立JSqlParser工程,本章对JSqlParser工程的代码结构作大致地介绍. 一.目录结构 JSqlParser的目录结构比较简单,主要有表达式,解析器,语句处理几个目录.下面这张图给出了一个具体的SQL语句与表达式: 接下来,简要介绍一下在JSqlParser中几个抽象概念. 二.SQL语句(statement) JSqlPaser将所有的SQL语句抽象为Statement,Sta

Linux中backtrace()系列函数的应用实例

一.引言 backtrace()系列函数可用来输出代码出错时的函数调用关系. A backtrace is the series of currently active function calls for the program. #include <execinfo.h> int backtrace(void **buffer, int size); char **backtrace_symbols(void *const *buffer, int size); void backtrac