网络编程
计算机之间真正进行通信的是网络应用程序。那么我们发送给一个IP 地址主机的数据应该由哪一个程序来接收呢,为了标示在计算机之间进行网络通信的程序,给某一个程序分配一个端口号,那么在发送时候,除了指定IP
计算机之间真正进行通信的是网络应用程序。那么我们发送给一个IP 地址主机的数据应该由哪一个程序来接收呢,为了标示在计算机之间进行网络通信的程序,给某一个程序分配一个端口号,那么在发送时候,除了指定IP 地址外,同时指定发送到哪一个端口,这样,在指定IP 地址的计算机上,就会有在这个端口上等待数据的网络应用程序去接收数据,网络通信和打电话是类似的,IP 地址就好像一个公司的总机的电话号码,端口就好像总机的分机号,我们发送的数据到了总机之后,相应的要转到分机上。
关于IP 地址:一,IP 网络中每台主机都必须有一个惟一的IP 地址;二,IP 地址是一个逻辑地址;三,因特网上的IP 地址具有全球唯一性;四,IP 地址是由32位,4个字节来表示的,常用点分十进制的格式表示。
协议
为进行网络中的数据交换(通信)而建立的规则、标准或约定。(—=语义 语法 规则);不同层具有各自不同的协议。程序员应该掌握的网络基本知识:
多种通信媒介——有线、无线、、、、、;
不同种类的设备——通用、专用。。。。
不同的操作系统——Unix,Windows „„
不同的应用环境——固定,移动。。。。。
不同业务种类——分时,交互,实时。。。。。
宝贵的投资和积累——有形、无形。。。。
用户业务的延续性---不允许出现大的跌宕起伏。
它们互相交织,形成了非常复杂的系统应用环境。
网络异质性问题的解决
网络体系结构就是使这些用不同媒介连接起来的不同设备和网络系统在不同的应用环境下实现互操作性,并满足各种业务需求的一种粘合剂,它营造了一种生存空间——任何厂商的任何产品、以及任何技术只要遵守这个空间的行为规则,就能够在其中生存并发展。网络体系结构解决异质性问题采用的是分层方法——把复杂的网络问题划分为若干个较小、单一的问题,在不同层上予以解决。就像我们在编程时百问题分解为很多小的模块来解决一样。 ISO/OSI七层参考模型
ISO 国际标准化组织,提出了OSI(Open System Interconnection)七层参考模型,OSI 将网络的不同功能划分为7层. 从下往上依次是物理层,第一层是提供二进制传输,确定如何在通讯信道上传输比特流,第二层是数据链路层,提供介质访问,加强物理层的传输功能,建立一条无差错的传输线路,第三层是网络层,提供IP 寻址和路由,因为在网络上数据到达一个目的地可以有多条线路,网络层
就负责找出最佳传输线路。第四层是传输层,传输层为源端主机和到目的端主机提供可靠的数据传输服务,隔离网络的上下层协议,使得网络应用与下层协议无关,第五层是会话层,会话层在两个相互通信的应用进程之间建立组织和协调相互之间通信,第六次是表示层,表示层处理被传输数据的表示问题,即信息的语法和语义,如有必要,使用一种通用的数据表示格式,在多种数据表示格式之间,进行转换,例如在货币,日期,数值,等本地数据表示格式与标准数据表示格式之间进行转换,还有数据的加解密,压缩,解压缩,等。第七层是应用层为用户的应用程序提供网络通信服务。要注意的是:OSI 参考模型并不是物理实体上存在这七层,这只是功能上的划分,是抽象的参考模型,在我们进行一次网络通信的时候,每一层负责为我们这次通信提供本层的功能。通信实体的对等层不允许直接通信。 我们一个通讯实体和另外一个实体进行通信,在他们的对等层之间不允许直接进行通信。各层之间是严格单向依赖。上层使用下层提供的服务—Service user 即服务的使用者; 下层向上层提供服务-Service provider. 作为服务的提供者。对等层实体之间虚拟通信,下层向上层提供
,服务,实际通信在最底层完成。在一个实体和另外一个实体进行通信时候,应用层发送的数据经过表示层,会话层 ,传输层,最后到达物理层,物理层将数据传输到另外一个实体的物理层,然后数据以此向上传递,最终到达应用层。这就是两个通信实体在通讯时候数据进行传输的过程。对等层实体之间是一个虚拟的通信。下层向上层提供服务,实际通信是在最底层完成的。
OSI各层所使用的协议
应用层:远程登录协议Telnet, 文件传输协议FTP ,超文本传输协议HTTP, 域名服务DNS, 简单邮件传输协议SMTP, 邮局协议POP3等。我们在网上下载一个软件就使用FTP 协议,HTTP 是使用的比较多的协议,我们在上网的时候,通过浏览器访问一个网页,就会使用超文本传输协议,DNS 也是使用的比较多的协议,我们通过网络访问一台主机时候,我们很少直接输入他的IP 地址,经常会使用这台主机的域名,通过DNS, 就可以将域名解析为他所对应的IP 地址,通过IP 就可以访问到对应的主机。我们通过Mail 发送邮件的时候,就会使用SMTP 协议,我们利用Mail 从263信箱中收取邮件的时候,就使用POP 协议。
传输层:传输控制协议TCP, 用户数据报协议UDP.
TCP:面向连接的可靠的传输协议。我们在利用TCP 协议进行通信的时候,首先经过三步握手,建立起通信双方的连接,一旦连接建立起,就可以进行通信了,TCP 提供了数据确认和数据重传的机制,保证了发送的数据一定能够到达通信的对方。这就像我们使用电话通信一样,首先拨打电话号码,建立一个连接,一旦电话拨通,连接建立之后,我们所说的每一句话都能传输到与之通信的对方。
UDP:是无连接的,不可靠的传输协议。采用该协议进行通信时候,不需要建立连接,我们可以直接向一个IP 地址发送数据,至于这个数据对方能否收到,不能保证了,我们知道在网络上传输的是电信号,既然是电信号,在传输的时候就会有衰竭,所以数据可能在网络上消失了,也有可能制定的IP 地址还没有分配,或者说具有这个IP 地址的主机还没有运行,都有可能导致发送的数据接收不到,和递信一样,信件有可能在运送途中丢失,也有可能收信的人搬家了,都有导致信件的丢失,我们在递信的时候不需要和对方认识,也就是不需要建立连接。既然该协议有这么多缺点,为什么还要用呢,主要就是该协议不需要建立连接,而且没有数据确认和数据重传的机制,所以实时性较高,在一些实时性要求较高的场合,例如视频会议,视频点播,就可以采用该协议来实现,因为对于这些数据来说,丢失少量的数据,捕获影响我们观看视频,而在数据完整性要求较高的场合,就可以采用TCP 协议,例如从网络上下载一个安装程序,如果丢失数据,安装程序无法应用。
网络层:网际协议IP,Internet 互联网控制报文协议ICMP,Internet 组管理协议IGMP. 数据封装
一台计算机要发送数据到另一台计算机,数据首先必须打包,打包的过程成为封装。封装就是在数据前面加上特定的协议头部。比如利用TCP 协议进行通信,当数据到达传输层的时候,当数据到达传输层时候要加上HTTP 头,当数据到达网络层时候,在数据前面加上IP 头。 OSI 参考模型中,对等层协议之间交换的信息单元统称为协议数据单元(PDU,Protocol Data Unit ). 为了提供服务,下层把上层的PDU 作为本层的数据封装,然后加入本层的头部(和尾部)。头部中含有完成数据传输所需的控制信息。这样,数据自上而下递交的过程实际上就是不断封装的过程。到达目的地后自下而上递交的过程就是不断拆封的过程,由此可知,在物理线路上传输的数据,其外面实际上被包封了多层“信封”。但是,某一层只能识别由对等层封装的“信封”,而对于被封装在“信封”内部的数据仅仅是拆封后将其提交给上层,本层不做任何处理。
TCP/IP模型
TCP/IP起源于美国国防部高级研究规划署(DARPA )的一项研究计划——实现若干台主机的
,相互通信。现在TCP/IP已成为Internet 上通信的工业标准。TCP/IP模型包括4个层次。应用层,传输层,网络层,网络接口层。该模型网络接口层对应了OSI 的数据链路层和物理层。网络层对应OSI 网络层,传输层对应传输层,应用层对应OSI 的应用层,表示层以及会话层。
在论述用JAVA 语言编写网络应用程序之前,先论述端口的概念。
按照OSI 七层模型的描述,传输层提供进程(应用程序)通信的能力,为了标示通信实体中进行通信的进程(应用程序),TCP/IP协议提出了协议端口(protocol port)的概念。 端口是一种抽象的软件结构(包括一些数据结构和I/O缓冲区)。应用程序通过系统调用与某端口建立连接(binding ,也就是应用程序绑定到个端口)后,传输层传给该端口的数据都被相应的进程所接收,相应进程发给传输层的数据都通过该端口输出。端口用一个整数型标识符来表示,即端口号。端口号跟协议相关。TCP/IP传输层的两个协议TCP 和UDP 是完全独立的两个软件模块,因此各自的端口号也相应独立,端口通常称为协议端口(protocol port ), 简称端口。端口使用一个16位数字来表示,也就是两个字节,范围是0-65535,1024以下的端口号保留给预定义的服务,因此在网络编程时采用1024以上的端口号。例如http 使用80端口。
套接字(Socekt )的引用
为了能够方便的开发网络应用软件,由美国伯克利大学在Unix 上推出了一种应用程序访问通信协议的操作系统调用socket ,套接字的出现,使程序员很方便的访问TCP/IP,从而开发各种网络应用的程序。随着Unix 的应用推广,套接字在编写网络软件中得到了极大的普及。后来,套接字又被引进了Windows 等操作系统中,成为开发网络应用程序的非常有效快捷的工具。
套接字存在于通信区域中,通信区域也叫做地址族,它是一个抽象的概念,主要用于通过套接字通信的进程的共有特性综合在一起,套接字通常只与同一区域的套接字交换数据(也有可能跨区域通信,但这只在执行了某种转换进程后才能实现)。Windows Socket 只支持一个通信区域:网际域(AF_INET), 这个域被使用网际协议簇通信的进程使用。
网络字节顺序
不同的计算机存放多字节值的顺序不同,有的机器在起始地址存放地位字节(低位先存),有的机器在起始地址存放高位字节(高位先存)。基于Intel 的CPU, 即我们常用的pc 机采用的是地位先存。为保证数据的正确性,在网络协议中需要指定网络字节顺序。TCP/IP协议使用16位整数和32位整数的高位先存格式。
客户机/服务器模式
在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户机/服务器模式,即客户机向服务器提出请求,服务器接收到请求后,提供相应的服务。
客户机/服务器模式的建立基于以下两点:首先,建立网络的起因是网络中软硬件资源,运算能力和信息不均等,需要共享,从而造就拥有众多资源的主机提供服务,资源较少的客户请求服务这一非对等作用。其次,网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是基于客户机/服务器模式TCP/IP。
客户机/服务器模式在操作过程中采取的是主动请求方式。首先服务器方要先启动,并根据请求提供相应的服务:
打开一个通信通道并告知本地主机,它愿意在某一地址和端口上接收客户请求。 等待客户请求到达该端口。
接收到重复服务请求,处理该请求并发送应答信号。接收到并发服务请求,要激活一个新的进程(或线程)来处理这个客户请求。新进程(或线程)处理此客户请求,并不需要对其他
,请求作出应答,服务完成后,关闭此新进程与客户的通信链路,并终止。
返回第二步,等待另一客户的请求。
关闭服务器。
客户方:
打开一个通信通道,并连接到服务器所在主机的特定端口。
向服务器服务请求报文,等待并接收应答;继续提出请求。
请求结束后关闭通信通道并终止。
Windows Sockets的实现
Windows Sockets是Microsoft Windows的网络程序设计接口,它是从Berkeley Sockets扩展而来的,以动态链接库的形式提供给我们使用。Windows Sockets在继承了Berkeley Sockets 主要特征的基础上,又对它进行了重要扩充。这些扩充主要是提供了一些异步函数,并增加了符合Windows 消息驱动特性的网络事件异步选择机制。
Windows Sockets1.1和Berkeley Sockets都是基于TCP/IP协议的;Windows Sockets2从Windows Sockets1.1发展而来,Windows Sockets1.1中的很多函数和伯克利套接字中的函数都是一致的,也就是如果采用双方共有的函数编写程序,那么我们的网络程序很容易移植到其他的系统平台下,与协议无关并向下兼容,可以使用任何底层传输协议提供的通信能力,来为上层应用程序完成网络数据通讯,而不关心底层网络链路的通讯情况,真正实现了底层网络通讯对应用程序的透明。
套接字的类型
流式套接字(SOCK_STREAM)
提供面向连接、可靠的数据传输服务,数据无差错、无重复的发送,且按发送顺序接收。 数据报套接字(SOCK_DGRAM)
提供无连接服务。数据包以独立形式发送,不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。
原始套接字(SOCK_RAW).
基于TCP 的socket 编程
服务器端程序:
1、创建套接字(socket )
2、将套接字绑定到一个本地地址和端口上。(bind)
3、将套接字设为监听模式,准备接收客户请求。(listen)
4、等待客户请求到来;当请求到来后,结束后连接请求,返回一个新的对应于此连接的套接字。(accept)
5、用返回的 和客户端进行通信。(send/recv)
6、返回,等待另一客户请求。
7、关闭套接字。
客户端程序:
1、创建套接字(socket)
2、向服务器发出连接请求(connect)
3、和服务器端进行通信。(send/recv)
4、关闭套接字。
基于UDP (面向无连接)的socket 编程
服务器端程序:(接收端)
,创建套接字(socket ).
将套接字绑定到本地地址和端口上(bind )
等待接收数据(recvfrom )
关闭套接字。
客户端(发送端)程序:
创建套接字(SOCKET )
向服务器发送数据(sendto ).
关闭套接字。
那么基于UDP 的网络编程为什么要绑定呢,虽然面向无连接的套接字不需要建立连接,但是要作为我们要完成这次通讯的话,对于我们接收端必须先启动,来接收客户端发送的数据,所以对于接收端来说,它也要告诉本地主机,它是哪一个地址上和端口上等待数据的到来,所以要调用bind. 注意面向无连接调用的是sendto,recvfrom, 而面向连接的调用是send/recv,
在视频实例中实际编写代码中,第一步,要利用套接字需要加载套接字库,这要调用函数WSAStartup. 其声明如下:
int WSAStartup(__in WORD wVersionRequested , __out LPWSADATA lpWSAData );
功能是加载套接字库,进行套接字库的版本协商。wVersionRequested 参数用于指定准备加载的Winsock 库的版本。高位字节指定所需要的Winsock 库的副版本。而低位字节则是主版本,我们通常见到的版本号类似于2.1,2就是主版本号,1就是福版本号,可以用MAKWORD 这个宏来得到一个WORD 值,可用MAKEWORD(x,y)(其中x 是高位字节,y 是低位字节) 方便地获得wVersionRequested 的正确值。lpWSAData 参数是指向WSADATA 结构的指针,该函数用其加载的库版本有关的信息填在这个结构中。
关于struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN 1];
char szSystemStatus[WSASYSSTATUS_LEN 1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
};
WSAStartup 把第一个字段wVersion 设为或是打算使用的Winsock 版本,wHighVersion 参数容纳的是现有的Winsock 库的最高版本,这两个字段中,高位字节代表的是Winsock 副版本,而低位字节代表的则是Winsock 主版本。szDescription 和szSystemStatus 这两个字段由特定的Winsock 实施方案设定,事实上没有用,不要使用下面这两个字段:iMaxSockets 和iMaxUdpDg ,它们是假定同时最多可打开多少套接字和数据报的最大长度。然而要知道数据报的最大长度应该通过WSAEnumProtocols 来查询协议信息。同时最多可打开套接字的数目不是固定的,很大程度上和可用物理内存的多少有关。最后。lpVendorInfo 字段是为Winsock 实施方案有关的制定厂商信息预留的,任何应该Win32平台上都没有使用这个字段。
如果Winsockdll 或底层网络系统没有被正确初始化或没有被找到,WSAStartup 将返回WSASYSNOTREADY, 此外这个函数允许你的应用程序协商使用某种版本的Winsock 规范,如果我们请求的版本等于或高于DLL 所支持的最低版本,WSAData 的成员wVersion 成员中将包含你的应用程序应该使用的版本,它是DLL 所支持的最低版本与请求版本中较小的那个。反之,如果请求的版本低于DLL 所支持的最低版本,WSAStartup 将返回WSAVERNOTSUPPORTED.
,关于WSAStartup 更详细的信息,查阅相关MSDN.
对于每一个WSAStartup 的成功调用(成功加载WinsockDLL 后),在最后都对应应该WSACLeanUp 调用,以便释放为应用程序分配的资源,终止对WinsockDLL 的使用。 一个小知识整理一个代码段中代码,使其对齐可以使用ALT F8键。
关于SOCKET socket(int af,int type,int protocol)的说明
SOCKET WSAAPI socket( __in int af, __in int type, __in int protocol
);
该函数接收三个参数,第一个参数指定地址族,对于TCP/IP协议的套接字,它只能是AF_INET(也可写成PF_INET).第二个参数指定Socket 类型,对于1.1版本的Socket, 它只支持两种类型的套接字,SOCK_STREAM指定产生流式套接字(基于TCP/IP协议),SOCK_DGRAM产生数据报套接字(基于UDP 协议)。第三个参数是与特定的地址家族相关的协议,如果指定为0,那么它就会根据地址格式和套接字类别,自动为你选择一个合适的协议。这是推荐使用的一种选择协议的方法。
如果这个函数的调用成功,它将返回一个新的SOCKET 数据类型的套接字描述符。如果调用失败,这个函数就会返回一个INVALID_SOCKET,错误信息可以通过WSAGetLastError 函数返回。关于函数
WSAGetLastError :
int WSAGetLastError(void);
关于bind 函数的说明
声明:int bind(
__in SOCKET s ,
__in const struct sockaddr* name ,
__in int namelen
); 这个函数将本地地址和一个套接字关联起来,该函数接收三个参数,第一个参数指定要绑定的套接字,第二个参数指定了该套接字的本地地址信息,是指向sockaddr 结构的指针变量,由于该地址结构是为所有的地址家族准备的,这个结构可能(通常会)随所使用的网络协议不同而不同,所以,要用第三个参数指定该地址结构的长度。Sockaddr 结构定义如下: Struct sockaddr{ u_short sa_farmily;char sa_data[14]};
Sockaddr 的第一个字段指定该地址家族,在这里必须设为AF_INET.第二个字段仅仅是标示要求一块内存分配区,起到占位的作用,该区域中指定与协议相关的具体地址信息。由于实际要求的只是内存区,所以对于不同的协议家族,用不同的结构来替换sockaddr. 处了第一字段外,sockaddr 是按网络字节顺序标示的,在TCP/IP中,我们可以用sockaddr_in结构替换sockaddr, 以方便填写信息。
关于sockaddr_in信息:
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct sin_addr;
char sin_zero[8];
,};第一个参数标示地址族,对于IP 地址,该成员将一直是AF_INET.第二个参数指定的是将要分配给套接字的端口,第三个参数给出的套接字的主机IP 地址。要注意这个IP 地址和端口号都需要用网络字节顺序来表示,是一个结构体变量:in_addr:
typedef struct in_addr {
union {
struct {
u_char s_b1,s_b2,s_b3,s_b4;
} S_un_b;
struct {
u_short s_w1,s_w2;
} S_un_w;
u_long S_addr; } S_un;
} IN_ADDR,
*PIN_ADDR,
FAR *LPIN_ADDR;
而最后一个参数只是一个填充数,以使该结构和sockaddr 结构的长度一样。如果这个函数调用成功,它将返回0. 如果调用失败,这个函数就会返回一个SOCKET_ERROR,错误信息可以通过WSAGetLastError 函数返回。
int listen(
__in SOCKET s ,
__in int backlog
);
功能是将套接字处于监听状态,监听链接请求,第一个参数是套接字描述符,第二个参数是等待连接队列的最大长度,如果设置这个参数为SOMAXCONN, 那么下层的服务提供者将负责套接字设置backlog 成最大的合理的值。注意设置这个参数backlog 主要是用来设置等待连接队列的最大长度,而不是说在一个端口上同时可以进行连接的数目。例如说将这个值设为2,那么这个时候有三个客户连接请求到来,那么前面两个连接请求就会被放到等待请求连接队列中,然后由我们应用程序依次为这些请求服务,而第三个连接请求就被拒绝了, SOCKET accept(
__in SOCKET s ,
__out struct sockaddr* addr ,
__in_out int* addrlen
);
第一个参数是套接字描述符,第二个参数是指向一个buffer 指针,用来接收连接实体的地址,也就是说当一个客户端向服务器端发起请求连接的时候,在我们接收连接的时候,用这个参数保存了发起连接的客户端的IP 地址信息以及它的端口信息,第三个参数是指向一个整形的指针,用来包含所返回的地址结构的长度。那么这个函数在msdn 中有一个问题就是最后一个参数是in out类型,表示这个参数必须要赋一个初始值,然后当你将这个实参传递进去之后,那么这个函数会返回一个值给你,如果说只是一个out, 那么在传递参数时候,不需要给它赋初始值,但是对于函数accept 在调用时候必须赋一个初始值,那么这个初始值大小就是结构体的长度,如果说你没有给这个参数赋初始值,那么这个调用就会失败。
,将IP 地址指定为INADDR_ANY,允许套接字向任何分配给本地机器的IP 地址发送或接收数据,多数情况下,每个机器只有一个IP 地址,但有的机器可能会有多个网卡每个网卡都可以有自己的IP 地址,用INADDR_ANY可以简化应用程序的编写。将地址指定为INADDR_ANY,允许一个独立应用接受发自多个接口的回应,如果我们只想让套接字使用多个IP 中的一个地址,就必须指定实际地址,要做到这一点,可以用inct_addr()函数,这个函数需要一个字符串作为其参数,该字符串指定了电分十进制表示的IP 地址(如192.168.0.16)。而且inet_addr()函数会返回一个适合分配给S_addr的u_long类型的数值。Inet_nota()函数会完成相反的转换,它接受一个in_addr结构体类型的参数并返回一个以电分十进制格式表示的IP 地址字符串。
在地址结构体sockaddr_in中给各个字段赋值,要注意在地址结构体中的这些成员除了sin_family之外,其他的成员都要使用网络字节顺序,要把INADDR_ANY转化为网络字节顺序,可以利用函数htonl 来完成,关于这个函数说明:功能是转换一个u_long类型从主机字节序到TCP/IP网络字节序,还有一个函数为htons ,功能是转换一个u_short类型,从主机字节序到TCP/IP网络字节序.
inet_addr()函数
unsigned long inet_addr( __in const char* cp
);
char* FAR inet_ntoa( __in struct in_addr in
);
接下来调用bind 函数;
将套接字设置为监听模式调用函数listen; 监听连接请求。
接下来用while 做一下死循环,因为作为我们服务器端来说,需要不断的等待客户端连接请求的到来。持续的运行下去。在循环体中需要调用accept, 关于函数accept: SOCKET accept(__in SOCKET s ,__out struct sockaddr* addr ,
__in_out int* addrlen ); 第一个参数是套接字描述符,第二个参数是一个out ,是指向一个buffer 指针,用来接收连接实体的地址,也就是当一个客户端向服务端发起连接请求,那么在接受这个连接的时候,通过该参数保存了发起连接的客户端的IP 地址信息和端口信息,第三个参数也是一个out, 也是一个返回值,它指向一个整形的指针,用来包含所返回的地址结构的长度,调用该函数的时候,需要定义一个地址结构体的变量。用来接收客户端的地址信息,还需要定义一个整形的变量,并赋予初始值为这个结构体的长度。那么要注意MSDN 文档有一个问题,通常我们看到如果前面写上一个in, 逗号,out, 就表示这个参数必须要赋予一个初始值,然后当你将这个参数传递进去之后,那么这个函数会再返回一个值给你,如果说只是一个out, 那么在传递这个参数的时候,不需要给该参数赋初始值,但是对于这个accept 函数来说,第三个参数在传递之前必须要赋初始值,那么这个初始值大小就是这个结构体长度,如果说没有给这个参数赋初始值,那么这个调用就会失败。该函数在接受客户端连接请求之后会返回一个相对于新的连接的套接字描述符,然后利用这个套接字可以和客户端进行通信了,而我们先前的套接字仍然继续监听客户端的连接请求。
到客户端的连接请求到来之后,我们接受了这个连接请求,建立了这个连接,同时返回了相对于这个连接的套接字,接下来就可以通讯了。我们可以向客户端发送数据使用函数
,send 来完成,所以在循环体中的调用accept 之后调用send.
Send 函数声明:
int send( __in SOCKET s, __in const char* buf, __in int len, __in int flags
);
第一个参数是套接字描述符,第二个参数是一个buffer, 包含了将要被传送的数据,第三个参数是buffer 中数据长度,第四个参数是一个标记,设置这个参数将影响send 函数的行为,在我们的应用中将其设置为零即可。
接收数据调用函数recv:
int recv( __in SOCKET s, __out char* buf, __in int len, __in int flags
); 第一个参数是建立连接之后的套接字,第二个是buffer 用来接收数据的,第三个参数是指示了buffer 的长度,第四个参数是标记和send 中的第四个参数类似,设置这个参数的值可以影响recv 之行为,在我们应用中将此参数设置为零即可。
连接服务器端调用函数connect:
int connect( __in SOCKET s, __in const struct sockaddr* name, __in int namelen
);
功能是建立一个连接,第一个参数是套接字描述符,第二个参数是一个地址结构体的一个指针,主要用来设定你所连接服务器端的地址信息,第三个参数是地址结构体的长度, Crtl tab键可以用来切换窗口。
基于CTP 连接的实例两个项目为TcpSrv,TcpClient, 分别为服务器端,客户端。 源文件代码为:
TcpSrv.cpp:
#include
#include"Winsock2.h"
#include
using namespace std;
void main()
{
//加载和请求套接字库;
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
,err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return ;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return ;
}
//创建套接字
SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);
//将创建的套接字绑定到本地地址和端口上
//为绑定本地地址和端口调用bind 创造条件,准备向其第二个参数传递实参, 用结构体类型sockaddr_in
//代替Sockaddr 以方便填写信息
sockaddr_in addrSrv;
//给sockaddr_in成员赋值;而其第三个成员sin_addr也是一个结构体, 在此结构体中S_un也是一个结构体,那么需要给这个
//结构体成员分别赋值, 并且调用htonl 将ulong 类型转化为网络字节序。
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(10000);
//条件成熟,下面调用bind;
bind(sockSrv,(sockaddr*)&addrSrv,sizeof (sockaddr));
//创建基于服务器端的socket ,调用listen 让服务器处于监听模式, 同时设置设置等待连接队列的最大长度
listen(sockSrv,5);
//用while 做死循环,让服务器端持续不断的运行下去;
//在循环之前需要为调用accept 创建条件,需要定义地址结构sockaddr_in变量,从而接收客户端的IP 和端口号。
//还需要定义一个整形变量来接收地址结构体sockaddr_in的长度,调用accept 在接收客户端连接请求之后,会返回
//一个相对于新的连接的套接字描述符。利用这个套接字描述符就可以和客户端进行通信了。而先前的套接字会继续监听
//客户的连接请求。
sockaddr_in addrClient;
int len=sizeof (sockaddr_in);
while (1)
{
SOCKET sockConn=accept(sockSrv,(sockaddr*)&addrClient,&len);