1.协议:为进行网络中的数据交换而建立的规则、标准或约定(语义+语法+规则)
(1)协议分层,各层之间单项依赖,下层向上层提供服务,对等层之间虚拟通信,实际通信在最底层完成
(2)应用层:远程登陆协议Telnet,文件传输协议FTP,超文本传输协议HTTP、域名服务DNS、简单邮件传输协议SMTP、邮局协议POP3
(3)传输层:传输控制协议TCP(面向连接,三次握手,数据确认和数据重传机制)
用户数据报协议UDP(无连接,实时性较高)
(4)网络层:网际协议IP,Internet互联网控制报文协议ICMP,Internet组管理协议IGMP
(5)数据封装:协议头
(6)端口:为了标识通信实体中进行通信的进程(应用程序),端口是一种抽象的软件结构(包括一些数据结构和I/O缓冲区)应用程序通过系统调用与某端口建立连接后,传输层传给该端口的数据都被相应的进程所接受,相应进程发给传输层的数据都通过该端口输出
我们在编写网络应用程序时,要为程序指定1024以上的端口号,因为1024以下的端口号保留给预定义的服务,如:http使用80端口
2.套接字(socket)
(1)套接字存在于通讯区域中,通信区域也叫地址族,它是一个抽象的概念,主要用于将通过套接字通信的进程的共有特征综合在一起。Windows Sockets只支持一个通信区域:网际层(AF_INET),这个域被使用网际协议簇通信的进程使用
(2)网络字节顺序:基于Intel的CPU,即我们常用的PC机采用的是低位先存,为保证数据的正确性,在网络协议中需要指定网络字节顺序,TCP/IP协议使用16位或32位整数的最高位先存格式
(3)客户机/服务器模式:网间通信是异步的,相互通信的进程间既不存在父子关系,又不存在共享内存缓冲区,需要一种机制为希望通行的进程间建立联系,为二者的数据交换提供同步,这就是基于客户机/服务器模式的TCP/IP
3.Windows Sockets的实现
(1)套接字类型
1.流式套接字(SOCK_STREAM):提供面向连接的、可靠的数据传输服务,数据无差错,无重复的发送,且按发送顺序接受,流式套接字实际上是基于TCP协议实现的
2.数据报套接字(SOCK_DGRAM):提供无连接服务,数据包以独立包形式发送,不提供无错保证,数据可能丢失或重复,并且接受顺序混乱,基于UDP协议实现
3.原始套接字(SOCK_RAW)
(2)基于TCP(面向连接)的socket编程
服务器端:
1.创建套接字(socket)
2.将套接字绑定到一个本地地址和端口上(bind)
3.将套接字设为监听模式,准备接受客户请求(listen)
4.等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)
5.用返回的套接字和客户端进行通信(send/recv)
6.返回,等待另一个客户请求
7.关闭套接字
客户端:
1.创建套接字(socket)
2.向服务器发送连接请求(connect)
3.和服务器端进行通信(send/recv)
4.关闭套接字
(3)基于UDP(面向无连接)的socket编程(对于UDP的套接字程序来说,它的服务器端和客户端这种概念不是很强化(接收端—发送端))
接收端
1.创建套接字(socket)
2.将套接字绑定到一个本地地址和端口上(bind)
3.等待接收数据(recvfrom)
4.关闭套接字
客户端
1.创建套接字(socket)
2.向服务器发送数据(sendto)
3.关闭套接字
4.相关函数
(1)WSAStartup函数:加载套接字库,进行套接字库的版本协商(确定使用的版本)
函数原型:int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData);
//第一个参数指定准备加载的Winsock库的版本
// lpWSAData返回值,指向WSADATA结构的指针,库版本有关的信息填充在这个结构中
对于每一个WSAStartup函数的成功调用(即成功加载WinSock动态库后)在最后都应调用一个WSACleanUp调用,以便释放为该应用程序分配的资源,终止对WinSock动态库的使用
(2)socket函数:创建套接字
函数原型:SOCKET socket(int af,int type,int protocol);
//第一个参数指定地址族,对于TCP/IP协议的套接字,只能是AF_INET(或PF_INET)
//第二个参数指定Socket类型(SOCK_STREAM和SOCK_DGRAM)
//第三个参数是与特定的地址家族相关的协议,若为0,那么系统会自动选择
(3)bind函数:将套接字绑定到本地的某个地址和端口上
函数原型:int bind(SOCKET s,const struct sockaddr FAR* name,int namelen);
//第一个参数是要绑定的套接字
//第二个参数是该套接字的本地地址信息,这是一个指向sockaddr结构的指针变量,由于该地址结构是为所有地址家族准备的,这个结构可能会随所使用的网络协议不同而不同
//第三个参数指定该地址结构的长度
sockaddr结构:struct sockaddr{
u_short sa_family;//地址家族(TCP/IP协议必须设置为AF_INET)
char sa_data[14];//要求一块内存分配区,起到占位作用,对于不同的协议家族,用不同的结构来替换sockaddr(除了sa_family外,sockaddr_in是按网络字节顺序表示的)
}
sockaddr_in结构替换sockaddr
struct sockaddr_in{
short sin_family;//地址族(IP—AF_INET)
unsigned short sin_port;//端口
struct in_addr sin_addr;//主机IP地址
char sin_zero[8];//填充数(使sockaddr_in结构和sockaddr结构的长度一样)
};
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;
(4)将IP地址指定为INADDR_ANY,允许套接字向任何分配给本机的IP地址发送或接受数据
inet_addr函数:指定某一特定IP
函数原型:unsigned long inet_addr(const char FAR* cp);//参数是一个字符串,该字符串指定了以点分十进制格式表示的IP地址(eg:192.168.1.1),该函数会返回一个适合分配给S_addr的u_long类型的数值
inet_ntoa函数:与inet_addr作用相反
函数原型:char FAR* inet_ntoa(struct in_addr in);
(5)listen函数:将指定的套接字设置为监听模式
函数原型:int listen(SOCKET s,int backlog);
//第一个参数是套接字描述符
//第二个参数是等待连接队列的最大长度,如果设置为SOMAXCONN,那么下层的服务提供者将负责将这个套接字设置为最大的合理值
(6)accept函数:接受客户端发送的连接请求
函数原型:SOCKET accept(SOCKET s,struct sockaddr FAR* addr,int FAR* addrlen);
//第一个参数是套接字描述符,该套接字已经通过listen函数将其设置为监听状态
//第二个参数是指向一个缓冲区的指针,该缓冲区用来接收连接实体的地址,也就是当客户端向服务器发起连接,服务器接受这个连接时,保存客户端的IP地址和端口信息
//第三个参数是一个指向整型的指针,返回包含地址信息的长度
(7)send函数:通过一个已建立连接的套接字发送数据
函数原型:int send(SOCKET s,const char FAR* buf,int len,int flags);
//第一个参数是一个已建立连接的套接字,第二个参数指向存放将要传递的数据的缓冲区,//第三个参数是缓冲区的长度,第四个参数设定的值影响函数的行为,一般为0
(8)recv函数:从一个已连接的套接字接受数据
函数原型:int recv(SOCKET s,char FAR* buf,int len,int flags);
//套接字,接受数据的缓冲区,缓冲区长度,影响函数行为
(9)connect函数:与一个特定的套接字建立连接
函数原型:int connect(SOCKET s,const struct sockaddr FAR* name,int namelen);
//套接字,连接的服务器端地址信息,指定服务器端地址的长度
(10)recvfrom函数:接受一个数据报信息并保存源地址
函数原型:int recvfrom(
SOCKET s,//准备接受数据的套接字
char FAR* buf,//指向缓冲区的指针,用来接受数据
int len,//缓冲区的长度
int flags,//影响函数行为
struct sockaddr FAR* from,//指向地址结构体的指针,主要用来接受发送数据方的地址信息
int FAR* fromlen//整型指针,并且它是一个in/out类型的参数,表明在调用前需要给它指定一个初始值,函数会通过这个参数返回地址结构的大小
);
(11)sendto函数:向一个特定的目的方发送数据
函数原型:int sendto(SOCKET s,const char FAR* buf,int len,int flags,const struct sockaddr FAR* to,int tolen);
//套接字;缓冲区指针,存放要发送的数据;缓冲区中数据长度;影响函数调用行为;可选//指针,指定目标套接字的地址;上个参数中指定地址的长度
(12)htons和htonl函数(主机字节顺序转为TCP/IP网络字节顺序)
u_short htons(u_short hostshort);
u_long htonl(u_long hostlong);
5.基于TCP的网络应用程序的编写(先导入ws2_32.lib)
(1)服务器程序
#include<Winsock2.h>
#include<stdio.h>
int main()
{
WORD wVersionRequested; //保存WinSock库的版本号
wVersionRequested = MAKEWORD(1,1);//调用MAKEWORD宏创建一个包含了请求版本号的WORD值
WSADATA wsaData;
//保存WSAStartup函数返回值,若不为0则退出
int err = WSAStartup(wVersionRequested,&wsaData); //加载套接字库
if(err != 0)
{
return 1;
}
//判断wsaData.wVersion的低字节和高字节是否都为1,如果不是我们请求的值,退出
if( (LOBYTE(wsaData.wVersion) != 1) || (HIBYTE(wsaData.wVersion) != 1))
{
WSACleanup();
return 1;
}
//创建用于监听的套接字
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 Here",inet_ntoa(addrClient.sin_addr));
send(sockConn,sendBuf,strlen(sendBuf) + 1,0);//用已经建立连接的套接字发送
//strlen不算\0,所以要加1
//接收数据
char recvBuf[100];
recv(sockConn,recvBuf,100,0);//用已经建立连接的套接字接收
//打印接收的数据
printf("%s\n",recvBuf);
closesocket(sockConn);
}
return 0;
}
(2)客户端程序
#include<Winsock2.h>
#include<stdio.h>
int main()
{
//加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(1,1);
err = WSAStartup(wVersionRequested,&wsaData);
if(err != 0)
{
return 1;
}
if( (LOBYTE(wsaData.wVersion) != 1) || (HIBYTE(wsaData.wVersion) != 1))
{
WSACleanup();
return 1;
}
//创建套接字
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));
//接收数据
char recvBuf[100];
recv(sockClient,recvBuf,100,0);
printf("%s\n",recvBuf);
//发送数据
send(sockClient,"lisi",strlen("lisi") + 1,0);
//关闭套接字
closesocket(sockClient);
//关闭套接字库
WSACleanup();
return 0;
}
6. 基于UDP的网络应用程序的编写(先导入ws2_32.lib)
(1)服务器端
#include<Winsock2.h>
#include<stdio.h>
int main()
{
//加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(1,1);
err = WSAStartup(wVersionRequested,&wsaData);
if(err != 0)
{
return 1;
}
if( (LOBYTE(wsaData.wVersion) != 1) || (HIBYTE(wsaData.wVersion) != 1))
{
WSACleanup();
return 1;
}
//创建套接字
SOCKET sockSrv = socket(AF_INET,SOCK_DGRAM,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));
char recvBuf[100];
char sendBuf[100];
char tempBuf[200];
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
while(1)
{
//等待并接收数据
recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);
if('q' == recvBuf[0])
{
sendto(sockSrv,"q",strlen("q") + 1,0,(SOCKADDR*)&addrClient,len);
printf("Chat end!\n");
break;
}
sprintf(tempBuf,"%s say:%s",inet_ntoa(addrClient.sin_addr),recvBuf);
printf("%s\n",tempBuf);
//发送消息
printf("input\n");
gets(sendBuf);
sendto(sockSrv,sendBuf,strlen(sendBuf) + 1,0,(SOCKADDR*)&addrClient,len);
}
//关闭套接字
closesocket(sockSrv);
WSACleanup();
return 0;
}
(2)客户端
#include<Winsock2.h>
#include<stdio.h>
int main()
{
//加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(1,1);
err = WSAStartup(wVersionRequested,&wsaData);
if(err != 0)
{
return 1;
}
if( (LOBYTE(wsaData.wVersion) != 1) || (HIBYTE(wsaData.wVersion) != 1))
{
WSACleanup();
return 1;
}
//创建套接字
SOCKET sockClient = socket(AF_INET,SOCK_DGRAM,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);
char recvBuf[100];
char sendBuf[100];
char tempBuf[200];
int len = sizeof(SOCKADDR);
while(1)
{
//发送数据
printf("Input\n");
gets(sendBuf);
sendto(sockClient,sendBuf,strlen(sendBuf) + 1,0,(SOCKADDR*)&addrSrv,len);
//等待并接收数据
recvfrom(sockClient,recvBuf,100,0,(SOCKADDR*)&addrSrv,&len);
if('q' == recvBuf[0])
{
sendto(sockClient,"q",strlen("q") + 1,0,(SOCKADDR*)&addrSrv,len);
printf("Chat end!\n");
break;
}
sprintf(tempBuf,"%s say:%s",inet_ntoa(addrSrv.sin_addr),recvBuf);
printf("%s\n",tempBuf);
}
//关闭套接字
closesocket(sockClient);
WSACleanup();
return 0;
}