最简单的网络程序如图:编程
提示:IP地址就至关于一个公司的总机号码,端口号就至关于分机号码。在打电话时,拨通总机后,还须要转到分机上。服务器
(1)协议网络
·为进行网络中的数据交换(通讯)而创建的规则、标准或约定(=语义+语法+规则);并发
·不一样层具备各自不一样的协议;socket
(2)网路的情况ide
·多种通讯媒介---有线、无线···函数
·不一样种类的设备---通用、专用···this
·不一样的操做系统---UNIX、Windows···spa
·不一样的应用环境---固定、移动···操作系统
·不一样的业务种类---分时、交互、实时···
·宝贵的投资和积累---有形、无形···
·用户业务的延续性---不容许出现大的跌岩起伏;
他们互相交织,造成了很是复杂的系统应用环境。
(3)ISO/OSI七层参考模型
·物理层:提供二进制传输,肯定在通讯信道上如何传输比特流;
·数据链路层:提供介质访问,增强物理层的传输功能,创建一条无差错的传输线路;
·网络层:提供IP寻址和路由(网络上数据能够经由多条线路到达目的地,网络层负责找出最佳的传输线路);
·传输层:为源端主机到目的端主机提供可靠的数据传输服务,隔离网络的上下层协议,使得网络应用与下层协议无关;
·会话层:在两个相互通讯的应用进程之间创建、组织和协调其相互之间的通讯;
·表示层:处理被传送数据的表示问题,即信息的语法和语义;若有必要,可以使用一种通用的数据表示格式,在多种数据表示之间进行转换。例如在日期、货币、数值等本地数据表示格式和标准数据表示格式之间进行转换,还有数据的加解密、压缩和解压缩等;
·应用层:为用户的网络应用程序提供网络通讯的服务;
注意:在进行一次网络通讯时,每一层为本次通讯提供本次的服务(通讯实体的对等体之间不容许直接通讯);
各层之间是严格单向依赖;
上层使用下层提供的服务---Service user;
下层向上层提供服务---Service provider;
(图片引用于别处)
应用层、传输层、网络层各使用的协议:
应用层:Telnet(远程登陆协议)、FTP(文件传输协议)、HTTP(超文本传输协议)、DNS(域名服务)、SMTP(简单邮件传输协议)、POP3(邮局协议)等;
传输层:TCP(传输控制协议)、UDP(用户数据报协议);
网络层:网际协议IP、Internet互联网控制报文协议ICMP、Internet组管理协议IGMP;
(4)数据封装
·一台计算机要想另外一台计算机发送数据,首先必须将该数据打包,打包的过程成为封装(即:在数据前面加上特定的协议头部);
·PDU(协议数据单元):对等层协议之间交换的信息单元的统称;
·头部含有的数据中含有王城数据传输所需的控制信息;
(5)TCP/IP模型
(6)端口
·传输层提供进程(活动的应用程序)通讯的能力;为了标识通讯实体中进行通讯的进程(应用程序),TCP/IP协议提出了协议端口(protocol port,简称端口)的概念;
·端口用一个整数型标识符来表示,即端口号;端口号跟协议相关,TCP/IP传输层的两个协议TCP和UDP是彻底独立的两个软件模块,所以各自的端口号也相互独立;
·咱们在编写网络应用程序时,要为程序指定1024以上的端口号;1024如下端口号保留给预约义的服务;
(7)套接字的引入
·套接字存在于通讯区域中;通讯区域也叫地址族,主要用于将经过套接字通讯的进程的共有特性综合在一块儿;
·套接字一般只与同一区域的套接字交换数据(也有可能跨区域通讯,但这只在执行了某种转换进程后才能实现);
·Windows Sockets只支持一个通讯区域:网际域(AF_INET),这个域被使用网际协议簇通讯的进程使用;
(8)网络字节顺序
·不一样的计算机存放多字节的顺序不一样;
·基于Inter的CPU,采用的是低位先存。为保证数据的正确性,在网络协议中须要指定网络字节顺序,TCP/IP协议使用16位整数和32位整数的高位先存格式;
·网络中不一样主机间进行通讯时,要同一采用网络字节顺序;
(9)客户机/服务器模式
图片来自:http://pic002.cnblogs.com/images/2012/387401/2012111509190090.jpg
客户机/服务器在操做过程当中采用主动请求的方式,首先服务器方要先启动,并根据请求提供相应的服务:
①打开一个通讯通道并告知本地主机,他愿意在某一地址可端口上接收客户请求;
②等待客户请求到达该端口;
③接收到重复服务请求,处理请求并发送应答信息。接收到并发起服务请求,要激活一个新的进程(或线程)来处理这个客户请求。新进程(或线程)处理此客户请求,并不须要对其余请求作出应答。服务完成后,关闭此新进程与客户的通讯连接,并终止;
④返回第二步,等待另外一客户请求;
⑤关闭服务器;
客户方:
①打开一个通讯通道,并链接到服务器所在主机的特定端口;
②想服务器发送服务请求报文,等待并接收应答;继续提出请求;
③请求结束后关闭通讯通道并终止;
Socket是链接应用程序与网络驱动程序的桥梁,Socket在应用程序中建立,经过绑定操做与驱动程序创建关系。此后,应用程序送给Socket的数据,由Socket交给驱动程序向网络上发送出去。计算机从网络上收到与该Socket绑定的IP地址和端口号相关的数据后,由驱动程序交给Socket。应用程序即可从该Socket中提取接收到的数据。
(1)套接字的类型
·流式套接字(SOCK_STREAM)
提供面向链接、可靠的数据传输服务,数据乌差错、无重复的发送,且按接发送顺序接收;SOCK_STREAM是基于TCP协议实现的;
·数据报式套接字(SOCK_DGRAM)
提供无链接服务;数据包以独立包形式发送,不提供无差错保证,数据可能丢失和重复,而且接收顺序混乱;SOCK_DGRAM是基于 UDP协议实现的;
·原始套接字(SOCK_RAW)
(2)基于TCP(面向链接)的Socket编程
基于基于TCP(面向链接)的Socket编程的服务端程序流程以下:
①建立套接字(socket);
②将套接字绑定到一个本地地址和端口上(bind);(解释:告诉本地主机它打算在哪一个IP地址和哪一个端口上等待客户请求)
③将套接字设为监听模式,准备接收客户请求(listen);
④等待客户请求到来;当请求到来后,接受链接请求,返回一个新的对应于此链接的套接字(accept);
⑤用返回的套接字和客户端进行通讯(send/recv);
⑥返回,等待另外一个客户请求;
⑦关闭套接字;
基于基于TCP(面向链接)的Socket编程的客户端程序流程以下:
①建立套接字(socket);
②向服务器发出链接请求(connect);
③和服务器端进行通讯(send/recv);
④关闭套接字;
在服务器端,当调用accept函数时,程序就会等待,等待客户端调用connect函数发出链接请求,而后服务器端接收该请求,因而双方就创建了链接,以后,服务器端和客户端就能够利用send和recv函数进行通讯了。
(3)基于UDP(面向无链接)的socket编程
接收端(服务器端):先启动的一端;发送端(客户端):发送数据的一端;
接收端程序的编写:
①建立套接字(socket);
②将套接字绑定到一个本地地址和端口上(bind);(解释:接收端告诉本地主机,它是在哪一个地址和端口上等待数据的到来)
③等待接收数据(recvfrom);
④关闭套接字;
客户端程序的编写:
①建立套接字(socket);
②向服务器发送数据(sendto);
③关闭套接字;
提示:套接字表示了通讯的端点;利用套接字通讯与利用电话机通讯是同样的,套接字至关于电话机,IP地址至关于总机号码,端口号至关于分机。
(1)WSAStartup函数
int WSAStartup( WORD wVersionRequested, //指定准备加载的Winsock库的版本; LPWSADATA lpWSAData //是一个返回值,指向WSADATA结构的指针 ); //lpWSAdata:这是一个返回值,指向WSADATA结构的指针,WSAStartup函数用其加载的库版本有关的信息填在这个结构中;
·功能:①加载套接字库;
②进行套接字库的版本的协商(肯定将使用的socket版本);
·对于每个WSAStartup函数的成功调用(即成功加载WinSock动态库后),在最后对应一个WASCleanUp调用,来释放该程序占用的资源,终止对WinSock动态库的使用。
typedef struct WSAData { WORD wVersion; WORD wHighVersion; char szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYS_STATUS_LEN+1]; unsigned short iMaxSockets; unsigned short iMaxUdpDg; char FAR * lpVendorInfo; } WSADATA, *LPWSADATA;
·WSAStartup函数把WSADate结构中的第一个字段wVersion设置为打算使用的Winsock版本,wHighVersion字段容纳的是现有的Winsock库的最高版本;
·注意:这两个字段中,高位字节表明的是Winsock副版本,而低字节表明的则是Winsock朱版本;
(2)socket函数
SOCKET socket( int af, //指定地址簇,对于TCP/IP协议的套接字,它只能是AF_INET(或PF_INET); int type, //指定socket类型(SOCK_STREAM、SOCK_DGRAM) int protocol //与特定的地址家族相关的协议; );
·若是socket函数调用成功,它就会返回一个新的SOCKET数据类型的套接字描述符;调用失败,返回一个INVALID_SOCKET值,错误信息能够经过WSAGetLastError函数返回。
(3)bind函数
int bind( SOCKET s, //指定要绑定的套接字; const struct sockaddr FAR *name, //指向sockaddr结构的指针变量,指定了该套接字的本地地址信息; int namelen //指定sockaddr地址结构的长度; );
·name:指定了该套接字的本地地址信息;指向sockaddr结构的指针变量,因为该地址结构是为全部的地址家族准备的,这个结构可能随所使用的网络协议不一样而不一样,故:第三个参数(namelen)指定改地址结构的长度;
·功能:建立套接字成功以后,将该套接字绑定到本地的某个地址和端口上;
sockaddr结构定义以下:
struct sockaddr { u_short sa_family; char sa_data[14]; };
·sockaddr结构的第一个字段(sa_family)指定地址家族,对于TCP/IP协议的套接字,必须设置为AF_INET;
·第二个地段(sa_data)仅仅是表示要求一块内存分配区,起到占用的做用,该区域中指定与协议相关的具体地址信息;
·注意:因为实际要求的只是内存去,因此对于不一样的协议家族,用不一样的结构来替代sockaddr。处理sa_family外,sockaddr是按网络字节顺序表示的。
**在基于TCP/IP的socket编辑过程当中,能够用sockaddr_in结构替换sockaddr以方便咱们填写地址信息**。
sockaddr_in结构体的定义以下:
struct sockaddr_in{ short sin_family; //表示地址族;(对于IP地址,改变量一直是AF_INET) unsigned short sin_port; //指定将要分配给套接字的端口; IN_ADDR sin_addr; //给出套接字的主机IP地址; char sin_zero[8];}; //填充数(使sockaddr_in和sockaddr长度同样);
·sockaddr_in结构中sin_addr成员的类型是in_addr,该结构的定义以下所示:
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结构其实是一个联合,一般利用这个结构将一个点分十进制格式的IP地址转换为u_long类型,并将结果赋给成员S_addr。
(4)inet_addr和inet_intoa函数
unsigned long inet_addr( const char FAR *cp );
·inet_addr函数须要一个字符串做为其参数,该字符串指定了以点分十进制格式表示的IP地址(如192.168.0.16);并且inet_addr函数会返回一个适合分配给S_addr的u_long类型的数值;
char FAR * inet_ntoa( struct in_addr in );
·inet_ntoa函数完成与inet_addr相反的转换,它接收一个in_addr结构体类型的参数并返回一个以点分十进制格式表示的IP地址字符串;
(5)listen函数
int listen( SOCKET s, // 套接字描述符; int backlog // 等待**链接队列**的最大长度; );
·做用:将指定的套接字设置为监听模式;
(6)accept函数
SOCKET accept( SOCKET s, //套接字描述符,该套接字已经经过listen函数将其设置为监听状态; struct sockaddr FAR *addr, int FAR *addrlen //是一个返回值,指向一个整型的指针,返回包含地址信息的长度; );
·功能:接受客户端发出的链接请求;
·addr参数:指向一个缓冲区的指针,该缓冲区用来接收链接实体的地址,也就是当客户端服务器发起链接,服务器接受这个链接时,保存发起链接的这个客户端的IP地址信息和端口信息;
(7)send函数
int send( SOCKET s, //一个已经创建链接的套接字; const char FAR *buf, //buf指向一个缓冲区,该缓冲区包含将要传递的数据; int len, //len是缓冲区的长度; int flags //flogs:设定的值将影响函数的行为,通常将器设置为0便可; );
·功能:经过一个已创建链接的套接字发送数据;
(8)recv函数
int recv( SOCKET s, //s:创建链接后准备接收数据的那个套接字; char FAR *buf, //buf:指向缓冲区的指针,用来保存接收的数据; int len, //len:缓冲区的长度; int flags //同send的flags; );
·功能:从一个已链接的套接字接收数据;
(9)connect
int connect( SOCKET s, //s:即将在其上创建链接的那个套接字; const struct sockaddr FAR *name, //name:设定链接的服务器地址信息; int namelen //namelen:指定服务器端地址的长度; );
·功能:将与一个特定的套接字创建链接;
(10)recvfrom
int recvfrom( SOCKET s, //s:准备接收数据的套接字; char FAR* buf, //buf:指向缓冲区的指针,该缓冲区用来接收数据; int len, //len:缓冲区长度; int flags, //不解释 struct sockaddr FAR *from, //from:是一个指向地址结构的指针,主要是用来接收发送数据方的地址信息; int FAR *fromlen //整型指针,且是一个in/out类型的参数; );
·功能:将接收一个数据报信息并保存源地址;
·fromlen:是一个in/out类型的参数,代表在调用前须要给它指定一个初始值,当函数调用以后,会经过这个参数返回一个值,该返回值是底地址结构的大小;
(11)sendto
int sendto( SOCKET s, const char FAR *buf, int len, int flags, const struct sockaddr FAR *to, //可选的指针,指定目标套接字的地址; int tolen //tolen:是参数to中指定的地址的长度; );
·功能:将向一个特定的目的方发送数据;
(12)htons 和htonl 函数
u_short htons( u_short hostshort //hostshort:是一个以主机字节顺序表示的16为数值; );
·功能:(Windows Sockets的htons函数)将把一个u_short类型的值从主机字节顺序转换为TCP/IP网络字节顺序;
u_long htonl( u_long hostlong //是一个以主机字节顺序表示的32位数值; );
·功能:将把一个u_long类型的值从主机字节顺序转换为TCP/IP网络字节顺序;
服务器端程序:
#include <stdio.h> #include <Winsock2.h> void main() { //加载套接字库 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); //MAKEWORD宏建立一个包含一个请求版本号的WORD值; //MAKEWORD(x,y)宏(x是高位字节, y是低位字节)能够方便的获取wVersionRequested的正确值; err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; } //①建立用于监听的套接字 SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000); bind(sockSrv, (sockaddr*)&addrSrv, sizeof(SOCKADDR) ); //②绑定套接字 listen(sockSrv, 5); //③将套接字设为监听模式,准备就收客户请求 SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); while(1) { SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len); //④等待客户请求到来 char sendBuf[100]; sprintf(sendBuf, "welcome %s to http://www.cnblog.com.aze-003", inet_ntoa(addrClient.sin_addr)); send( sockConn, sendBuf, strlen(sendBuf)+1, 0); //发送数据 注意:“+1”:表示增长1个“\0”结尾标志; char recvBuf[100]; recv( sockConn, recvBuf, 100, 0); //接收数据 printf("%s\n", recvBuf); //打印接收的数据 closesocket(sockConn); //⑦关闭套接字 } } /* ④等待客户请求到来;当请求到来后,接收链接请求,返回一个新的对应于这次链接的套接字(accept) ⑤用返回的套接字和客户端进行链接通讯(send/recv); ⑥返回,等待另外一客户请求; 注意:在调用accept函数前,必须为它的第三个参数赋予一个初始值,即:SOCKADDR_IN结构体的长度; 进入循环,首先调用accept函数等待并接收客户的链接请求,其中第一个参数是处于监听状态的套接字; 第二个参数利用addrClient变量接收客户端的地址信息。当客户端链接请求到来时,该函数接受该请求,建 立链接,同时它将返回一个相对于当前这个新链接的一个套接字描述符,保存于sockConn变量中,而后利用 这个套i蛾子就能够与客户端进行通讯了,而咱们先前的套接字仍然继续监听客户端的链接请求; */
客户端程序:
#include <stdio.h> #include <Winsock2.h> void main() { //加载套接字库 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; } //①建立套接字 SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000); //②向服务器发出链接请求 connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); //③和服务器进行通讯(send/recv) char recvBuf[100]; recv(sockClient, recvBuf, 100, 0); //接收数据 printf("%s\n", recvBuf); send(sockClient, "this is lisi", strlen("this is lisi")+1, 0); //发送数据 closesocket(sockClient); //④关闭套接字 WSACleanup(); }
服务器端程序:
//UDP_Srv.cpp #include <stdio.h> #include <Winsock2.h> void main() { //加载套接字库 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; } //①建立套接字 SOCKET sockSrv = socket( AF_INET, SOCK_DGRAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = htonl(ADDR_ANY); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000); //②绑定套接字 bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); //③等待并接收数据 (UDP服务器就是一个接收端) SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); char recvBuf[100]; recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len); printf("%s\n", recvBuf); closesocket(sockSrv); //④关闭套接字 WSACleanup(); }
客户端程序:
//UDP_Client.cpp #include<WINSOCK2.H> #include<STDIO.H> void main() { //加载套接字库 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; } //建立套接字 SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0); SOCKADDR_IN sockSrv; sockSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); sockSrv.sin_family = AF_INET; sockSrv.sin_port = htons(6000); //发送数据 sendto(sockClient, "hello!", strlen("hello!")+1, 0, (SOCKADDR*)&sockSrv, sizeof(SOCKADDR)); //关闭套接字 closesocket(sockClient); WSACleanup(); }
2014-08-14
23:56:21