码迷,mamicode.com
首页 > Windows程序 > 详细

winsocket之TCP/UDP编程

时间:2016-04-29 20:22:01      阅读:408      评论:0      收藏:0      [点我收藏+]

标签:winsocket之tcp/udp编程

一.概述:

本次练习的是TCP/UDP套接字编程,使用的是winsocket,对主要的库函数进行简绍,并实现了一个程序:实现服务器与客户端之间的通信,在服务器端实现记录用户名和密码,客服端可以实现用户名和密码的输入和查找,并且检查是否匹配。(参考  <<Visual C++网络编程>>)

PS: 127.0.0.1是回路地址,用于在同一台机器上测试代码。端口号要大于1024。



二.基于TCP/UDP协议的套接字编程详解:

技术分享

基于 TCP 的套接字编程的所有客户端和服务器端都是从调用socket 开始,它返回一个套接字描述符。客户端随后调用connect 函数,服务器端则调用 bind、listen 和accept 函数。套接字通常使用标准的close 函数关闭,但是也可以使用 shutdown 函数关闭套接字。下面针对套接字编程实现过程中所调用的函数进程分析。以下是基于 TCP 套接字编程的流程图:


技术分享


典型的UDP客户/服务器程序函数调用图:

技术分享




三.相关数据结构:

(1).sockaddr:sockaddr用来保存一个套接字

struct sockaddr
{
    unsigned short int sa_family; //指定通信地址类型,如果是TCP/IP通信,则值为AF_inet
    char sa_data[14]; //最多用14个字符长度,用来保存IP地址和端口信息};
 }

(2).

sockaddr_in的功能与socdaddr相同,也是用来保存一个套接字的信息,不同的是将IP地址与端口分开为不同的成员,定义如下:

struct sockaddr_in
{
    unsigned short int sin_family; //指定通信地址类型
    uint16_t sin_port; //套接字使用的端口号
    struct in_addr sin_addr; //需要访问的IP地址
    unsigned char sin_zero[8]; //未使用的字段,填充为0};
 }

在这一结构中,in_addr也是一个结构体,定义如下,用于保存一个IP地址:

struct in_addr
{
    uint32_t  s_addr;
};

(3).WSAData:包含Winsock库的版本信息,这个结构是在调用函数WSAStartup时由系统填入。

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; 
}; 
    wVersion为你将使用的Winsock版本号,

    wHighVersion为载入的Winsock动态库支持的最高版本,注意,它们的高字节代表次版本,低字节代表主版本。
    szDescription与szSystemStatus由特定版本的Winsock设置,实际上没有太大用处。
    iMaxSockets表示最大数量的并发Sockets,其值依赖于可使用的硬件资源。
    iMaxUdpDg表示数据报的最大长度;然而,获取数据报的最大长度,你需要使用WSAEnumProtocols对协议进行查询。

(4).SOCKET:即套接字句柄,为一个32位的整数。

typedef unsigned int SOCKET




四.Winsock相关函数:

(1).WSAStartup函数:初始化Winsock

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData)

 wVersionRequested参数:是一个WORD(双字节)数值,它指定了应用程序需要使用的Winsock版本.
    主版本号在 低字节, 次版本号在 高字节。

    实例:希望版本号为 1.2 可以如下代码:
    wVersionRequested = 0x0201。

    lpWSAData参数:指向WSADATA数据结构的指针,该结构用于返回本机的Winsock系统实现的信息.
    该结构WhighVersion和wVersion两个域系统支持的最高版本,后者是系统希望调用者使用的版本.

 函数成功 返回0; 否则返回错误码.

一般可以这样初始化:


WSADATA wsaData;

WSAStartup(0x0202, &wsaData); 


(2).WSACleanup函数:Winsock程序在退出之前都必须要调用WSAClernup,以便系统可以释放资源。

int WSACleanup(void)


(3).WSAGetLastError函数:当一个Winsock函数返回一个失败值时,调用这个函数可以获取具体的失败原因。

int WSAGetLastError(void)


(4).inet_addr函数:地址转换函数

ussigned long inet_addr(const char FAR* cp)

例如:inet_addr("127.0.0.1")


(5).字节序转换函数:

u_long htonl(u_long hostlong);

u_short htons(u_short hostshort);

u_long ntonl(u_long netlong);

u_short ntons(u_short netshort);

htonl和htons用于把主机号字节序转换为网络字节序,ntonl和ntons则相反。(host:主机)



五.与实现通信相关的函数:

(1).sock函数:返回一个 套接字句柄。

int socket(int family, int type, int protocol);   


  1.  * 说明:  

  2.  * socket类似与open对普通文件操作一样,都是返回描述符,后续的操作都是基于该描述符;  

  3.  * family 表示套接字的通信域,不同的取值决定了socket的地址类型,其一般取值如下:  

  4.  * (1)AF_INET         IPv4因特网域  

  5.  * (2)AF_INET6        IPv6因特网域  

  6.  * (3)AF_UNIX         Unix域  

  7.  * (4)AF_ROUTE        路由套接字  

  8.  * (5)AF_KEY          密钥套接字  

  9.  * (6)AF_UNSPEC       未指定  

  10.  *  

  11.  * type确定socket的类型,常用类型如下:  

  12.  * (1)SOCK_STREAM     有序、可靠、双向的面向连接字节流套接字  

  13.  * (2)SOCK_DGRAM      长度固定的、无连接的不可靠数据报套接字  

  14.  * (3)SOCK_RAW        原始套接字  

  15.  * (4)SOCK_SEQPACKET  长度固定、有序、可靠的面向连接的有序分组套接字  

  16.  *  

  17.  * protocol指定协议,常用取值如下:  

  18.  * (1)0               选择type类型对应的默认协议  

  19.  * (2)IPPROTO_TCP     TCP传输协议  

  20.  * (3)IPPROTO_UDP     UDP传输协议  

  21.  * (4)IPPROTO_SCTP    SCTP传输协议  

  22.  * (5)IPPROTO_TIPC    TIPC传输协议  


(2).closesocket函数:关闭一个套接字。

int closesocket(SOCKET s);

传回值: 成功 返回0 , 失败 返回 SOCKET_ERROR 。


(3).shutdown函数:停止 Socket 接收/传送的功能

int shutdown(SOCKET s,int how)

参数: s :Socket 的识别码,

          how :代表该停止那些动作的标帜 

传回值: 成功返回 0 ,失败 返回 SOCKET_ERROR 。
若 how 的值为 0,则不再接收资料。 
若 how 的值为 1,则不再允许传送资料。 
若 how 的值为 2,则不再接收且不再传送资料。 
注意:shutdown() 函式并没有将 Socket 关闭,所以该 Socket 所占用之资源必须在呼叫closesocket() 之后才会释放。 


(4).bind函数:把相关套接字句柄绑定到addr地址,绑定之后客户端/服务器就可以通过该地址连接到服务器/客户端。

int bind(SOCKET s, const struct sockaddr *addr, socklen_t addrlen);   

   s参数: 为一个套接字句柄;  

   addr参数:是一个指向特定协议地址结构的指针;  

   addrlen参数:是地址结构的长度;  

传回值: 成功 返回0 , 失败 返回 SOCKET_ERROR 。

PS:当addr结构体中的sin_addr.s_addr为INADDR_ANY时,表示绑定到通配地址(即没有指定的mac地址),这时可以接受来自所有网络接口的连接请求,适合于有多个网卡的情况。


(5).listen函数:设定 Socket 为监听状态,准备被连接。

int listen(SOCKET s,int backlog)

参 数: s Socket 的识别码,

              backlog 未真正完成连接前彼端的连接要求的最大个数 (连接队列中的最大数)
传回值: 成功 返回0 , 失败 返回 SOCKET_ERROR 。


(6).accept函数:从已完成连接队列队头返回下一个已完成连接;若已完成连接队列为空,则进程进入睡眠。

   int accept(SOCKER s, struct sockaddr FAR* addr, int FAR* addrlen)     

参数 s:已经绑定并进入监听状态的套接字句柄。

   addr:用于保存客户端的地址信息。

   addrlen:用于保存客户端的地址信息的结构体大小

PS:若没有连接请求等待处理,accept会阻塞直到一个请求到来;  

返回值:若成功返回套接字描述符,出错返回INVALID_SOCKET;如果成功,此时的套接字为已连接套接字,后面的通信是用这个已连接套接字来描述的。


(7).recv函数:用于TCP流式套接字编程中接受来自客户端的消息。

int recv(SOCKET s, char FAR* buf, int len, int flags)

参数:s:accept返回的一个已连接套接字

    buf:用于接受数据的缓存区

    len:buf的长度,以字节问单位。

    flags:用来影响recv的行为,一般为0;

返回值:成功时,返回实际接收的字节数,失败返回SOCKET_ERROR。

PS:recv为阻塞式的等待有数据可接收。


(8).send函数:用于TCP流式套接字编程中发送消息到客户端。

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

参数:s:accept返回的一个已连接套接字

    buf:用于发送数据的缓存区

    len:buf的长度,以字节问单位。

    flags:用来影响recv的行为,一般为0;

返回值:成功时,返回实际发送的字节数,失败返回SOCKET_ERROR。


(9).connect函数:连接服务器地址,使s成为一个已连接套接字。

int connect(SOCKET s, const struct sockaddr *addr, socklen_t addrlen);   

   s参数: 为一个套接字句柄;  

   addr参数:是一个指向特定协议地址结构的指针;  

   addrlen参数:是地址结构的长度;  

传回值: 成功 返回0 ,此时代表已经和服务器连接成功,s成为一个已连接套接字; 失败 返回 SOCKET_ERROR 。


(10).recvfrom函数用于UDP报文套接字编程中接受来自地址为from的主机消息。

int recvfrom(SOCKET s, char FAR* buf, int len, int flags,

         struct socket FAR* from, int FAR* fromlen)

参数:s为一个已连接套接字。

    buf:数据接收缓冲区。

    len:数据接收缓存区的大小。

    flags:用于控制recvfrom行为的一些标志,一般为0.

    from:对方的套接字地址。

    fromlen:对方的套接字地址结构的大小。

返回值:成功返回实际接受的字节数,失败返回SOCKET_ERROR;如果buf大小不足以容纳接收到的数据报,那么返回一个WSAEMSGSIZE错误。

PS:UDP套接字编程中的接收和发送信息都是以数据报为单位的。每次recvfrom都是接收一个独立的数据报,不同的recvfrom所接收的数据报之间没有任何关系(即不会出现一个recvfrom接收一个数据报的一部分,另一个接收这个数据报的另一部分的情况)。


(11)..sendto函数用于UDP报文套接字编程中发送到地址为from的主机消息。

int sendto(SOCKET s, char FAR* buf, int len, int flags,

         struct socket FAR* to, int FAR* tolen)

参数:s为一个已连接套接字。

    buf:发送数据的缓冲区。

    len:发送数据的缓存区大小。

    flags:用于控制sendto行为的一些标志,一般为0.

    to:对方的套接字地址。

    tolen:对方的套接字地址结构的大小。

返回值:成功返回实际发送的字节数,失败返回SOCKET_ERROR;要发送的字节数为len时,如果len大于UDP数据报的最大值,那么返回一个WSAEMSGSIZE错误。

PS:对于UDP而言,sendto的行为是全部或者没有(all or nothing),要么成功发送所有要求发送的字节,要么发送失败。




六.实际步骤参考上图。




七.实现功能的代码:

(1)TCP套接字编程:

服务器:

#include<iostream>
#include<string.h>
#include<winsock2.h>
#pragma comment(lib,"Ws2_32.lib")
using namespace std;

struct Data
{
	char* _usser;
	char* _possword;

	Data()
		:_usser(new char[1024])
		, _possword(new char[1024])
	{}
};


const int PORT = 2000;
const int LEN = 1024;
char buf[LEN];
Data messeage[LEN];

SOCKET sListen;
sockaddr_in saListen; 
sockaddr_in saClient;
SOCKET serverToClient;

void Find()   //查找用户名 和 密码是否匹配存在
{
	char* msUsser = "请输入你要查找的用户名:";
	send(serverToClient, msUsser, strlen(msUsser), 0);

	memset(buf, 0, LEN);
	int ret = recv(serverToClient, buf, LEN, 0);//接收用户名
	for (size_t i = 0; i < LEN; i++)
	{
		if (strcmp(messeage[i]._usser,buf) == 0)
		{
			char* msPsw = "请输入所查找用户名的密码:";
			send(serverToClient, msPsw, strlen(msPsw), 0);

			memset(buf, 0, LEN);
			int ret = recv(serverToClient, buf, LEN, 0);//接收密码

			if (strcmp(messeage[i]._possword, buf) == 0)      //s注意用trcmp函数
			{
				char* ms = "用户名和密码匹配存在\n请选择->";
				send(serverToClient, ms, strlen(ms), 0);
			}
			else
			{
				char* mssage = "用户名正确 密码错误\n请选择->";
				send(serverToClient, mssage, strlen(mssage), 0);
			}

			memset(buf, 0, LEN);
			recv(serverToClient, buf, LEN, 0);
			return;
		}
	}

	char* msUsserNot = "用户名不存在\n请选择->";
	send(serverToClient, msUsserNot, strlen(msUsserNot), 0);

	memset(buf, 0, LEN);
	recv(serverToClient, buf, LEN, 0);
}

void Record()
{
	size_t index = 0;
	while (true)
	{	
		while(buf[0] == ‘2‘)    //只有用户名和密码都输入完才能查找//      while
		{
			Find();
		}

		if (buf[0] == ‘0‘)   //如果客服端输入0   则退出连接
		{
			char* ms = "退出成功";
			int len = strlen(ms);
			send(serverToClient, ms, len, 0);

			cout << "服务器退出连接!!!" << endl;
			return;
		}


		char* msUsser = "请输入用户名:";
		int len = strlen(msUsser);
		send(serverToClient, msUsser, len, 0);

		memset(buf, 0, LEN);
		int ret = recv(serverToClient, buf, LEN, 0);//接收用户名

		if (ret == SOCKET_ERROR)  //表示接收失败
		{
			cout << "Client closed !!!" << endl;
			int size = sizeof(sockaddr);
			serverToClient = accept(sListen, (sockaddr*)&saClient, &size);  //重新创建一个连接套接字 
			cout << "accept new" << endl;
		}

		strcpy_s(messeage[index]._usser,1024, buf);   //注意 用户名不用++

		char* msPsw = "请输入密码:";
		send(serverToClient, msPsw, strlen(msPsw), 0);

		memset(buf, 0, LEN);
		int retTwo = recv(serverToClient, buf, LEN, 0);//接收密码

		if (retTwo == SOCKET_ERROR)  //表示接收失败
		{
			cout << "Client closed !!!" << endl;
			int size = sizeof(sockaddr);
			serverToClient = accept(sListen, (sockaddr*)&saClient, &size);  //重新创建一个连接套接字 
			cout << "accept new" << endl;
		}

		strcpy_s(messeage[index++]._possword,1024, buf);
		memset(buf, 0, LEN);

		char* choice = "请选择->";
		send(serverToClient, choice, strlen(choice), 0);

		memset(buf, 0, LEN);
		recv(serverToClient, buf, LEN, 0);//接收客户的选择
	}
}


int main()
{
	WSADATA wsaData;
	WSAStartup(0x0202, &wsaData);   //初始化winsock

	sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  //创建一个套接字

	//绑定分配的 port 和 ip地址
	saListen.sin_family = AF_INET;
	saListen.sin_addr.s_addr = htonl(INADDR_ANY);
	saListen.sin_port = htons(PORT);
	bind(sListen, (sockaddr*)&saListen, sizeof(sockaddr));

	listen(sListen, 5);      //进入监听状态

	int size = sizeof(sockaddr);
    serverToClient = accept(sListen, (sockaddr*)&saClient, &size);   //创建一个连接套接字
	
	int ret = recv(serverToClient, buf, LEN, 0);
	if (ret == SOCKET_ERROR)  //表示接收失败
	{
		cout << "Client closed !!!" << endl;
		int size = sizeof(sockaddr);
		serverToClient = accept(sListen, (sockaddr*)&saClient, &size);  //重新创建一个连接套接字 
		cout << "accept new" << endl;
	}

	Record();
	

	return 0;
}





客户端:

//本次练习存在很多漏洞     
//当相同的代码段重复出现多次时  变量的命名很重要
//客户端只要接受和发送信息就可   关键协议在服务端实现
//注意  send  与 recv  要匹配使用

#include<iostream>
#include<winsock2.h>
#pragma comment(lib,"WS2_32.lib")
using namespace std;

const int PORT = 2000;
const int LEN = 1024;

int main()
{
	WSADATA wsaData;
	WSAStartup(0x0202, &wsaData);

	SOCKET client;
	client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	sockaddr_in saServer = {0};
	saServer.sin_family = AF_INET;
	saServer.sin_addr.s_addr = inet_addr("127.0.0.1");
	saServer.sin_port = htons(PORT);
	int ret = connect(client, (sockaddr*)&saServer, sizeof(saServer));

	if (ret == 0)
	{
		printf("connect access :%d:%d\n", saServer.sin_addr.s_addr, saServer.sin_port);	
	}
	else
	{
		printf("connect faild :%d:%d\n", saServer.sin_addr.s_addr, saServer.sin_port);
	}

	cout << "............0:退出    1:输入    2:查找 ..............." << endl;
	
	char buf[LEN];
	printf("请选择->  ");
	gets_s(buf);
	send(client, buf, strlen(buf), 0);

	while (true)
	{
		
		memset(buf, 0, LEN);
		int retUsser = recv(client, buf, LEN, 0);
		buf[retUsser] = ‘\0‘;
		cout << buf << endl;

		gets_s(buf);   
		send(client, buf, strlen(buf), 0);

	}

	return 0;
}

执行结果:

技术分享

技术分享

技术分享

技术分享



(2).UDP套接字编程:

服务器:

#include<iostream>
#include<string.h>
#include<winsock2.h>
#pragma comment(lib,"Ws2_32.lib")
using namespace std;

struct Data
{
	char* _usser;
	char* _possword;

	Data()
		:_usser(new char[1024])
		, _possword(new char[1024])
	{}
};


const int PORT = 2000;
const int LEN = 1024;
char buf[LEN];
Data messeage[LEN];

SOCKET sListen;           //服务器套接字
sockaddr_in saListen;     //本地套接字地址
sockaddr_in saClient;     //客户端套接字地址
//SOCKET serverToClient;    //已连接套接字

int clientSize = sizeof(saClient);       // ....改....

void Find()   //查找用户名 和 密码是否匹配存在
{
	char* msUsser = "请输入你要查找的用户名:";
	sendto(sListen, msUsser, strlen(msUsser), 0, (SOCKADDR*)&saClient, clientSize);    // ....改....

	memset(buf, 0, LEN);
	int ret = recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);//接收用户名    // ....改....
	for (size_t i = 0; i < LEN; i++)
	{
		if (strcmp(messeage[i]._usser, buf) == 0)
		{
			char* msPsw = "请输入所查找用户名的密码:";
			sendto(sListen, msPsw, strlen(msPsw), 0, (SOCKADDR*)&saClient, clientSize);      // ....改....

			memset(buf, 0, LEN);
			int ret = recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);//接收密码     // ....改....

			if (strcmp(messeage[i]._possword, buf) == 0)      //s注意用trcmp函数
			{
				char* ms = "用户名和密码匹配存在\n请选择->";
				sendto(sListen, ms, strlen(ms), 0, (SOCKADDR*)&saClient, clientSize);    // ....改....
			}
			else
			{
				char* mssage = "用户名正确 密码错误\n请选择->";
				sendto(sListen, mssage, strlen(mssage), 0, (SOCKADDR*)&saClient, clientSize);    // ....改....
			}

			memset(buf, 0, LEN);
			recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);     // ....改....
			return;
		}
	}

	char* msUsserNot = "用户名不存在\n请选择->";
	sendto(sListen, msUsserNot, strlen(msUsserNot), 0, (SOCKADDR*)&saClient, clientSize);   // ....改....

	memset(buf, 0, LEN);
	recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);     // ....改....
}

void Record()
{
	size_t index = 0;
	while (true)
	{
		while (buf[0] == ‘2‘)    //只有用户名和密码都输入完才能查找//      while
		{
			Find();
		}

		if (buf[0] == ‘0‘)   //如果客服端输入0   则退出连接
		{
			char* ms = "退出成功";
			int len = strlen(ms);
			sendto(sListen, ms, len, 0, (SOCKADDR*)&saClient, clientSize);    // ....改....


			cout << "服务器退出连接!!!" << endl;
			return;
		}


		char* msUsser = "请输入用户名:";
		int messageLen = strlen(msUsser);
		sendto(sListen, msUsser, messageLen, 0, (SOCKADDR*)&saClient, clientSize);  // ....改....

		memset(buf, 0, LEN);
		int ret = recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);//接收用户名    // ....改....

		if (ret == SOCKET_ERROR)  //表示接收失败
		{
			cout << "recvfrom error !!!" << endl;
//			int size = sizeof(sockaddr);                    // ....改.... 
//			serverToClient = accept(sListen, (sockaddr*)&saClient, &size);  //重新创建一个连接套接字   // ....改....
//			cout << "accept new" << endl;      // ....改....
		}

		strcpy_s(messeage[index]._usser, 1024, buf);   //注意 用户名不用++

		char* msPsw = "请输入密码:";
		sendto(sListen, msPsw, strlen(msPsw), 0, (SOCKADDR*)&saClient, clientSize);      // ....改....

		memset(buf, 0, LEN);
		int retTwo = recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);//接收密码       // ....改....

		if (retTwo == SOCKET_ERROR)  //表示接收失败
		{
			cout << "recvfrom error !!! !!!" << endl;
//			int size = sizeof(sockaddr);          // ....改....
//			serverToClient = accept(sListen, (sockaddr*)&saClient, &size);  //重新创建一个连接套接字        // ....改....
//			cout << "accept new" << endl;          // ....改....
		}

		strcpy_s(messeage[index++]._possword, 1024, buf);
		memset(buf, 0, LEN);

		char* choice = "请选择->";
		sendto(sListen, choice, strlen(choice), 0, (SOCKADDR*)&saClient, clientSize);      // ....改....

		memset(buf, 0, LEN);
		recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);//接收客户的选择    // ....改....
	}
}


int main()
{
	WSADATA wsaData;
	WSAStartup(0x0202, &wsaData);   //初始化winsock

	sListen = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);  //创建一个套接字     ....改....

	//绑定分配的 port 和 ip地址
	saListen.sin_family = AF_INET;
	saListen.sin_addr.s_addr = htonl(INADDR_ANY);
	saListen.sin_port = htons(PORT);
	bind(sListen, (sockaddr*)&saListen, sizeof(sockaddr));

//	listen(sListen, 5);      //进入监听状态

//	int size = sizeof(sockaddr);
//	serverToClient = accept(sListen, (sockaddr*)&saClient, &size);   //创建一个连接套接字

	int ret = recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);     // ....改....
	if (ret == SOCKET_ERROR)  //表示接收失败
	{
		cout << "recvfrom error !!!" << endl;
//		int size = sizeof(sockaddr);                     // ....改....
//		serverToClient = accept(sListen, (sockaddr*)&saClient, &size);  //重新创建一个连接套接字       // ....改....
//		cout << "accept new" << endl;                    // ....改....
	}

	Record();


	return 0;
}



客户端:

//本次练习存在很多漏洞     
//当相同的代码段重复出现多次时  变量的命名很重要
//客户端只要接受和发送信息就可   关键协议在服务端实现
//注意  send  与 recv  要匹配使用

#include<iostream>
#include<winsock2.h>
#pragma comment(lib,"WS2_32.lib")
using namespace std;

const int PORT = 2000;
const int LEN = 1024;

int main()
{
	WSADATA wsaData;
	WSAStartup(0x0202, &wsaData);

	SOCKET client;
	client = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);      // ....改....

	sockaddr_in saServer = { 0 };
	saServer.sin_family = AF_INET;
	saServer.sin_addr.s_addr = inet_addr("127.0.0.1");
	saServer.sin_port = htons(PORT);
	int ret = connect(client, (sockaddr*)&saServer, sizeof(saServer));

	if (ret == 0)
	{
		printf("connect access :%d:%d\n", saServer.sin_addr.s_addr, saServer.sin_port);
	}
	else
	{
		printf("connect faild :%d:%d\n", saServer.sin_addr.s_addr, saServer.sin_port);
	}

	cout << "............0:退出    1:输入    2:查找 ..............." << endl;

	char buf[LEN];
	int serverSize = sizeof(saServer);     // ....改....
	printf("请选择->  ");
	gets_s(buf);
	int retlen = sendto(client, buf, strlen(buf), 0, (SOCKADDR*)&saServer, sizeof(saServer));   // ....改....
	if (retlen == WSAEMSGSIZE)    // ....改....
	{
		cout << "over size !" << endl;
	}
	else if (retlen == 0)
	{
		cout << "sendto error !" << endl;
	}

	while (true)
	{

		memset(buf, 0, LEN);
		int retUsser = recvfrom(client, buf, LEN, 0, (SOCKADDR*)&saServer, &serverSize);     // ....改....

		if (retlen == WSAEMSGSIZE)
		{
			cout << "over size !" << endl;
		}
		else if (retlen == SOCKET_ERROR)
		{
			cout << "sendto error !" << endl;
		}

		buf[retUsser] = ‘\0‘;
		cout << buf << endl;

		gets_s(buf);
		sendto(client, buf, strlen(buf), 0, (SOCKADDR*)&saServer, sizeof(saServer));     // ....改....

	}

	return 0;
}



执行结果:

技术分享

技术分享





总结:TCP是面向连接的协议,所以TCP的套接字编程要bind本地套接字地址,进入listen状态,accept得到一个已连接套接字,之后才能接收和发送消息,而UDP是面向无连接的,所以只要bind本地套接字地址之后就可以发送和接收消息了。

服务器要先接收消息再发送信息,而客户端是先发送信息再接收消息。

客服端一般可以省略绑定本地套接字地址,因为系统会自动为客服端分配一个本地IP地址和本地端口。

本文出自 “水仙花” 博客,请务必保留此出处http://10704527.blog.51cto.com/10694527/1768961

winsocket之TCP/UDP编程

标签:winsocket之tcp/udp编程

原文地址:http://10704527.blog.51cto.com/10694527/1768961

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!