[精通WindowsSocket网络开发-基于VC++实现]第三章——WindowsSockets基础—TCP,UDP程序

时间:2022-04-11 14:52:26

TCP程序

 [精通WindowsSocket网络开发-基于VC++实现]第三章——WindowsSockets基础—TCP,UDP程序

TCPServer

// TCPServer.cpp : 定义控制台应用程序的入口点。
//接收客户的发来的"MyTCP"
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#define BUF_SIZE 64
void main()
{
WSADATA wsd;
if (WSAStartup(MAKEWORD(2,2),&wsd) != 0)//初始化套接字动态库
{
printf("WSAStartup() failed! erron=%d\n",GetLastError());
return;
}
SOCKET sServ;
if ((sServ=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == INVALID_SOCKET)//创建套接字
{
printf("socket() failed! errno=%d\n",WSAGetLastError());
WSACleanup();
return;
}

//绑定服务器地址
SOCKADDR_IN addrServ;
addrServ.sin_family=AF_INET;
addrServ.sin_addr.S_un.S_addr=INADDR_ANY;
addrServ.sin_port=htons(5000);
if (bind(sServ,(SOCKADDR*)&addrServ,sizeof(addrServ)) == SOCKET_ERROR)
{
printf("bind() failed!errno=%d\n",WSAGetLastError());
closesocket(sServ);//关闭套接字
WSACleanup();//释放套接字资源
return;
}

if (listen(sServ,2) == SOCKET_ERROR)//开始监听
{
printf("listen() failed! errno=%\n",WSAGetLastError());
closesocket(sServ);
WSACleanup();
return;
}

SOCKADDR_IN addrClient;
SOCKET sClient;
int len=sizeof(addrClient);
if ((sClient=accept(sServ,(SOCKADDR*)&addrClient,&len)) == INVALID_SOCKET)//接收客户端连接
{
printf("accept() failed! error=%d\n",WSAGetLastError());
closesocket(sServ);
WSACleanup();
return;
}

//接收客户端数据
char buf[BUF_SIZE];
ZeroMemory(buf,BUF_SIZE);
if (recv(sClient,buf,BUF_SIZE,0) == SOCKET_ERROR)
{
printf("recv() failed! error=%d\n",WSAGetLastError());
closesocket(sClient);
closesocket(sServ);
WSACleanup();
return;
}
printf("%s\n",buf);//输出“MyTCP”
closesocket(sClient);
closesocket(sServ);
WSACleanup();
system("pause");
return;
}
//相对完整的http://blog.csdn.net/ouyangshima/article/details/8932334
/*
TCP编程的服务器端一般步骤是
1.创建一个socket,用函数socket();
2.设置socket属性,用函数setsockopt(); //可选
3.绑定IP地址,端口等信息到socket上,用函数bind();
4.开启监听,用函数listen();
5.接收客户端上的来的连接,用函数accept();
6.收到数据用send()和recv(),或者read()和write();
7.关闭网络连接
8.关闭监听
服务器端 socket-->bind-->listen-->accept
*/

TCPClient 

// TCPClient.cpp : 定义控制台应用程序的入口点。
//功能:向服务器端发送“MyTCP”
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#define BUF_SIZE 64
void main()
{
WSADATA wsd;
if (WSAStartup(MAKEWORD(2,2),&wsd) != 0)//初始化套接字动态库
{
printf("WSAStartup() failed! erron=%d\n",GetLastError());
return;
}

SOCKET sHost;//服务器套接字
if ((sHost=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == INVALID_SOCKET)//创建套接字
{
printf("socket() failed! errno=%d\n",WSAGetLastError());
WSACleanup();//释放套接字资源
return;
}

//设置服务器地址
SOCKADDR_IN servAddr;
servAddr.sin_family=AF_INET;
servAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
servAddr.sin_port=htons(5000);
if (connect(sHost,(SOCKADDR*)&servAddr,sizeof(servAddr)) == SOCKET_ERROR)//连接套接字
{
printf("connect() failed! errno=%d\n",WSAGetLastError());
closesocket(sHost);//关闭套接字
WSACleanup();
return;
}

//向服务器发送数据
char buf[BUF_SIZE];
ZeroMemory(buf,BUF_SIZE);
strcpy(buf,"MyTCP");
if (send(sHost,buf,strlen(buf),0) == SOCKET_ERROR)
{
printf("send() failed! erron=%d\n",WSAGetLastError());
closesocket(sHost);
WSACleanup();
return;
}
//退出
closesocket(sHost);
WSACleanup();
system("pause");
return;
}
//相对完整的http://blog.csdn.net/ouyangshima/article/details/8922575
/*
TCP编程的客户端一般步骤是
1.创建一个socket,用函数sockect();
2.设置socket属性,用函数setsockopt(); //可选
3.绑定IP地址,端口等信息到socket上,用函数bind(); //可选
4.设置要连接的对方的IP地址和端口等属性;
5.连接服务器,用函数connect();
6.收到数据,用函数send()和recv()或者read()和write();
7.关闭网络连接
客户端 socket-->connect
*/

TCP API 

/*
1.WSAStartup()函数
WSAStartup():功能是加载ws2_32.dll等socket程序运行环境的动态库(DLL)。在程序初始化后,socket程序运行所依赖的动态链接库不一定已经加载,WSAStartup保证了Socket动态链接库的加载。
int WSAStartup(_in Word wVersionRequested,_out LPWSDATA lpWSAData);
wVersionRequested:是Socket程序库的版本。高字节指定所需要库文件的副版本,低字节指定主版本。在程序中可以使用MAKEWORD(X,Y)方便指定参数,其中X是指高位字节,Y是指低位字节。一般使用MAKEWORD(2,2)宏
lpWSAData:输出参数,指向WSADATA结构的指针,用于返回scoket库初始化的信息
返回值:0表示成功,
WSACleanup():与WSAStartup()的功能相反,WSACleanup释放ws2_32.dll库。
WSADATA wsaData;
LPVOID recvbuf;
if (WSAStartup(MAKEWORD(2,2),&wsaData) != NO_ERROR)
{
printf("Error at WSAStartup()\n");
}
if(LOBYTE(wsaData.wVersion)!=2 || HIBYTE(wsaData.wVersion) != 2)//检查返回的DLL是否为2.2
{
//没有找到可用的DLL
WSACleanup();
return;
}

2.socket()函数
SOCKET socket( int af, int type, int protocol ); 等价于WSASocket()
参数:int af:协议的地址家族(AF_UNIX,AF_INET等),AF_UNIX只能够用于单一的Unix系统进程间的通信,而AF_INET是针对Internet的,因此可以允许远程——本机之间的通信。AF:Address families(地址协议族)。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。创建TCP或者UDP套接字时,该参数为AF_INET
int type:协议的套接字类型:有SOCK_STREAM(TCP),SOCK_DGRAM(UDP),SOCK_RAM 3种
int protocol:指定协议(有IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议),由于我们指定了type,所以这个地方我们一般只要用0来代替就可以了
返回值:若成功返回SOCKET对象标识,SOCKET就是unsigned int;如失败,则返回INVALID_SOCKET,调用WSAGetLastError()可得知原因,所有WinSocket的函数都可以使用这个函数来获取失败的原因。
注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。

3.bind()函数
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。
bind()函数把一个地址族中的特定地址赋给socket。例如,对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
int bind(SOCKET s,sockaddr * name,int namelen);
参 数: s:Socket对象名,它通过socket()创建了,唯一标识一个socket
name:地址,服务器地址信息名称(包含信息有:地址协议族,服务器本机的IP,要监听的端口)
namelen:sockaddr的结构长度
返回值:成功返回0,否则返回SOCKET_ERROR
通常服务器在启动的时候会绑定一个众说周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它(ip+port)来连接服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不用调用,而是在connect()时由系统随机生成一个。
SOCKET s;
SOCKADDR_IN servAddr;
//定义服务器地址
servAddr.sin_family=AF_INET;
servAddr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//如果程序不关心分配给它的地址,则可将地址设置为INADDR_ANY,可以使用任意网络接口。
servAddr.sin_port=htons(2222);
if (bind(s,(SOCKADDR*)&servAddr,sizeof(servAddr)) == SOCKET_ERROR)//绑定服务器地址
{
//绑定套接字失败
}

4.listen()函数
int listen(SOCKET s,int backlog);将套接字设置为监听模式
参 数: s:一个已绑定的套接字
backlog:指定等待连接的最大队列长度(一般2~4,用SOMAXCONN则有系统确定)。socket可以排队的最大连接个数,不是最多可以连接几个客户端。更具体些:TCP模块允许的已完成三次握手过程(TCP模块完成)但还没来得及被应用程序accep()的最大连接数。eg:listen(socketID,3);如果有4个客户端同时向服务器发出请求,那么前3个连接会被放到等待处理的队列中,以便服务器依次为他们服务,而第4个连接将会造成WSAECONNREFUSED错误。当服务器接受了一个连接请求时,这个请求就从队列中删除。
返回值:成功返回0,否则返回SOCKET_ERROR
socket()函数创建的socket默认是一个主动类型的,listen()函数则将主动连接套接口socket变为被动连接套接口,使得这个进程可以接受其他进程的请求(客户的连接请求),从而成为一个服务器进程。

5.accept()函数
SOCKET accept(SOCKET s,_output struct sockaddr * addr,_output int * addrlen);
参数:s:监听套接字,
addr:存放来连接的客户端的地址和端口,(若客户端使用了bind()来绑定客户端本地的IP和Port,则服务器端会得到客户端bind的端口,而不是服务器端自动分配的端口)。当我们调用socket()创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。若对客户端的IP地址不感兴趣,则可以设为NULL
addrlen:返回addr结构的长度,sizeof(SOCKADDR_IN),当addr为NULL时,addrlen则可以为NULL
返回值:功返回一个新产生的Socket对象,否则返回INVALID_SOCKET
accept()默认会阻塞进程(所以需要一个单独的线程来等待客户端的连接),直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字就是连接套接字。
监听套接字:监听套接字正如accept()的参数sockfd,它就是监听套接字,在调用listen()函数之后。
连接套接字:一个套接字会从主动连接的套接字变为一个监听套接字;而accept()返回的是已连接socket描述字(一个连接套接字),它代表一个网络已经存在的点点连接。
一个服务器通常仅仅只创建一个监听socket描述符,它在该服务器的生命周期内一直存在。内核为每个有服务器进程接收的客户端连接创建了一个已连接的socket描述符,当服务器完成了对某个客户的服务后,相应的已连接socket描述符就被关闭。
连接套接字并没有占有新的端口与客户端通信,依然使用的是监听套接字一样的端口号。

6.recv()函数
int recv( SOCKET s, char FAR *buf, int len, int flags );等级与WSARecv()
参数:s:Socket 的识别码
buf:接收数据缓冲区
len:缓冲区的长度
flags:该参数影响该函数的行为,可以设为 0(表示无特殊行为),MSG_PEEK(会使有用的数据被复制到接收缓冲区内,但没有从系统缓冲区中将其删除),MSG_OOB(表示处理带外数据)
返回值:若成功则返回接收资料的长度,否则返回SOCKET_ERROR

7.send()函数
int send( SOCKET s, const char FAR *buf,int len, int flags );等价于WSASend()
参数:s:Socket 的识别码
buf:存放要传送数据的暂存区
len:发送数据的长度
flags:该参数影响该函数的行为,可以设为 0(表示无特殊行为),MSG_DONTROUTE(标志要求传输层不要将数据路由出去),MSG_OOB(表示该数据被带外发送)
返回值:若成功则返回发送的资料的长度,否则返回SOCKET_ERROR

8.closesocket()函数
int closesocket(SOCKET s);关闭套接字,释放所占资源
返回值:成功返回0,否则返回SOCKET_ERROR
9.shutdown()函数
int shutdown(SOCKET s, int how);用于通知对方不再发送数据,或者不再接收数据,或者既不发送也不接收数据
参数:s:套接字
how:参数为SD_RECEIVE(表示不允许再调用接收数据函数);SE_SEND(表示不允许再调用发送数据函数);SD_BOTH(表示既不允许调用发送数据函数也不允许调用接收数据函数);
返回值:成功返回0,否则返回-1

10.connect()函数
int connect(SOCKET s,const struct sockaddr FAR * name,int namelen);
参数:s:socket的标识码
name:存储服务器的连接信息(协议族,服务器的ip,端口),服务器地址;
namelen:sockaddr结构的长度
返回值:成功返回0,失败返回SOCKET_ERROR

11.字节转换函数
在网络上面有着许多类型的机器,这些机器在表示数据的字节顺序是不同的,比如i386芯片是低字节在内存地址的低端,高字节在高端,而alpha芯片却相反.为了统一起来,则有专门的字节转换函数.
unsigned long int htonl(unsigned long int hostlong)
unsigned short int htons(unisgned short int hostshort)
unsigned long int ntohl(unsigned long int netlong)
unsigned short int ntohs(unsigned short int netshort)
在这四个转换函数中,h代表host, n代表 network.s代表short l代表long
第一个函数的意义是将本机器上的long数据转化为网络上的long.其他几个函数的意义也差不多

12.地址结构说明
typedef struct sockaddr_in
{
short sin_family;//由于我们主要使用Internet,所以一般为AF_INET
u_short sin_port;//16位端口号,网络字节顺序
struct in_addr sin_addr;//32位IP地址,网络字节顺序
char sin_zero[8];//保留
}SOCKADDR_IN;
struct in_addr // Internet address.
{
uint32_t s_addr; // address in network byte order
};
//sin_:socket address internet
struct sockaddr是通用的套接字地址,而struct sockaddr_in则是internet环境下套接字的地址形式,二者长度一样,都是16个字节。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。一般情况下,需要把sockaddr_in结构强制转换成sockaddr结构再传入系统调用函数中。
*/

UDP程序

 [精通WindowsSocket网络开发-基于VC++实现]第三章——WindowsSockets基础—TCP,UDP程序

无连接协议的套接字调用时序

UDPServer

// UDPServer.cpp : 定义控制台应用程序的入口点。
//功能:接收客户端发来的"MyUDP"
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#define BUF_SIZE 64
void main()
{
WSADATA wsd;
if (WSAStartup(MAKEWORD(2,2),&wsd) != 0)//初始化套接字动态库
{
printf("WSAStartup failed,errno=%d\n",GetLastError());
return;
}
SOCKET sockServ;
if ((sockServ=socket(AF_INET,SOCK_DGRAM,0)) == INVALID_SOCKET)//创建套接字
{
printf("socket() failed,errno=%d\n",WSAGetLastError());
WSACleanup();//释放套接字资源
return;
}

int nBufLen;//接收数据缓冲区大小
int nOptLen=sizeof(nBufLen);
if (getsockopt(sockServ,SOL_SOCKET,SO_RCVBUF,(char*)&nBufLen,&nOptLen) == SOCKET_ERROR)//获取接收数据缓冲区大小
{
printf("getsockopt() failed,errno=%d\n",WSAGetLastError());
}

nBufLen *=10;//设置接收数据缓冲区为原来的10倍
if (setsockopt(sockServ,SOL_SOCKET,SO_RCVBUF,(char*)&nBufLen,nOptLen) == SOCKET_ERROR)
{
printf("setsockopt() failed,errno=%d\n",WSAGetLastError());
}

int newRcvBuf;//检查设置系统接收数据缓冲区是否成功
if (getsockopt(sockServ,SOL_SOCKET,SO_RCVBUF,(char*)&newRcvBuf,&nOptLen) == SOCKET_ERROR)
{
printf("second getsockopt() failed,errno=%d\n",WSAGetLastError());
}
if (newRcvBuf != nBufLen)
{
printf("set bufLen size=size*10 failed\n");
}

//设置服务器地址
SOCKADDR_IN addrServ;
addrServ.sin_family=AF_INET;
addrServ.sin_addr.s_addr=INADDR_ANY;
addrServ.sin_port=htons(5000);
if (bind(sockServ,(SOCKADDR*)&addrServ,sizeof(addrServ)) == SOCKET_ERROR)//绑定
{
printf("bind() failed,errno=%d\n",WSAGetLastError());
closesocket(sockServ);
WSACleanup();
return;
}

//接收数据
SOCKADDR_IN addrClient;
int addrClientLen=sizeof(addrClient);
char buf[BUF_SIZE];
ZeroMemory(buf,BUF_SIZE);
if (recvfrom(sockServ,buf,BUF_SIZE,0,(SOCKADDR*)&addrClient,&addrClientLen) == SOCKET_ERROR)
{
printf("recvfrom() failed,errno=%d\n",WSAGetLastError());
closesocket(sockServ);//关闭套接字
WSACleanup();
return;
}

printf("%s\n",buf);//输出 MyUDP
closesocket(sockServ);
WSACleanup();
system("pause");
return;
}
/*
UDP编程的服务器一般步骤是
1.创建一个socket,用函数socket();
2.设置socket属性,用函数setsockopt(); //可选
3.绑定IP地址,端口灯属性到socket上,用函数bind();
4.循环接收数据,用函数recvfrom();
5.关闭网络连接;
*/

UDPClient 

// UDPClient.cpp : 定义控制台应用程序的入口点。
//功能:向服务器端发送"MyUDP"
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#define BUF_SIZE 64
void main()
{
WSADATA wsd;
if (WSAStartup(MAKEWORD(2,2),&wsd) != 0)//初始化套接字动态库
{
printf("WSAStartup() failed,errno=%d\n",GetLastError());
return;
}
SOCKET sClient;
if ((sClient=socket(AF_INET,SOCK_DGRAM,0)) == INVALID_SOCKET)//创建套接字
{
printf("socket() failed,errno=%d\n",WSAGetLastError());
WSACleanup();//释放套接字资源
return;
}
char buf[BUF_SIZE];
ZeroMemory(buf,BUF_SIZE);
strcpy(buf,"MyUDP");

//服务器地址
SOCKADDR_IN addrServ;
int addrServLen=sizeof(addrServ);
addrServ.sin_family=AF_INET;
addrServ.sin_addr.s_addr=inet_addr("127.0.0.1");
addrServ.sin_port=htons(5000);
if (sendto(sClient,buf,BUF_SIZE,0,(SOCKADDR*)&addrServ,addrServLen) == SOCKET_ERROR)//发送数据
{
printf("sendto() failed,errno=%d\n",WSAGetLastError());
closesocket(sClient);//关闭套接字
WSACleanup();
return;
}
closesocket(sClient);
WSACleanup();
system("pause");
return;
}
/*
UDP编程的客户端的一般步骤是
1.创建一个socket,用函数socket();
2.设置socket属性,用函数setsockopt(); //可选
3.绑定IP地址,端口等信息到socket上,用函数bing(); //可选
4.设置对方的IP地址和端口等属性;
5.发送数据,用sendto();
6.关闭网络连接;
*/

UDP API 

/*
1.recvfrom()函数
int recvfrom(SOCKET s,char FAR* buf,int len,int flags,struct sockaddr FAR* from,int FAR* fromlen);用于接收数据,并且返回发送数据主机地址
buf:接收数据缓冲区
len:接收数据缓冲区的大小
flags:该参数影响recvfrom()函数的行为。参数可以为0(表示无特殊行为);MSG_PEEK(会使有用的数据被复制到接收缓冲区内,但没有从系统缓冲区中将其删除);MSG_OOB(表示处理带外数据);
from:该参数返回发送数据主机的地址
fromlen:地址长度
返回值:成功返回接收数据的字节数,否则返回SOCKET_ERROR

2.sendto()函数
int sendto(SOCKET s,const char FAR* buf,int len,int flags,const struct sockaddr FAR* to,int tolen);//发送数据
s:套接字
buf:发送数据的缓冲区
len:发送数据缓冲区的大小
flags:该参数影响sendto()函数的行为。参数可以为0(表示无特殊行为);MSG_DONTROUTE(标志要求传输层不要将数据路由出去);MSG_OOB(标志预示该数据应该被带外发送);
to:接收数据的地址
tolen:地址长度
返回值:成功则返回发送数据的字节数,否则返回SOCKET_ERROR
*/


/*
1.getsockopt()函数
int getsockopt(SOCKET s,int level,int optname,char FAR* optval,int FAR* optlen);用于获取套接字选项信息
s:套接字
level:选项级别,有SOL_SOCKET,IPPROTO_TCP两个级别
optname:套接字选项名称
optval:接收数据缓冲区,该参数返回套接字选项名称对应的值
optlen:缓冲区大小
返回值:成功返回0,否则返回SOCKET_ERROR

2.setsockopt()函数
int setsockopt(SOCKET s,int level,int optname,const char FAR* optval,int optlen);设置套接字选项
s:套接字
level:选项级别:有SOL_SOCKET和IPPROTO_TCP两个级别
optname:套接字选项名称
optval:该参数用于设置套接字选项的值
optlen:缓冲区大小
返回值:成功返回0,否则返回SOCKET_ERROR
*/