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地址和本地端口。

时间: 2024-11-28 23:34:12

winsocket之TCP/UDP编程的相关文章

TCP/UDP编程中的问题汇总

TCP/UDP编程中的问题汇总 TCP和UDP发送大文件的问题. 答: 发送端: 发送时,先发送文件的名称及大小等信息. 然后,设置一个缓冲区的大小,假设为4K. 再循环读4K的文件内容,并发送,直到发送完成. 最后,再发送完成标记. 接收端: 接收到第一个包时,得到文件的大小等信息. 计算出要接收多少个包. 然后,循环接收包,并将接收到的数据写入到文件中. 直到,接收到的数据长度等于文件的大小. struct package { 文件标识 //GUID 偏移量 //001- 数据段 //Byt

linux网络编程--tcp/udp编程模型

tcp 模型如下: 上面的模型已经很清楚了 具体函数用法就不细说了 请看tcp简单的例子: 其中server.c #include <stdio.h>#include <string.h>#include <stdlib.h>#include <sys/socket.h>#include <sys/types.h>#include <netinet/in.h>#include <arpa/inet.h> #define e

【Python学习之旅】---socket编程(基于TCP/UDP编程)

原文地址:https://www.cnblogs.com/chenyuxia/p/12271080.html

TCP/UDP网络编程的基础知识与基本示例(windows和Linux)

一.TCP编程的一般步骤 服务器端: 1.创建一个socket,用函数socket() 2.绑定IP地址.端口等信息到socket上,用函数bind() 3.开启监听,用函数listen() 4.接收客户端上来的连接,用函数accept() 5.收发数据,用函数send()和recv(),或者read()和write() 6.关闭网络连接 7.关闭监听 客户端: 1.创建一个socket,用函数socket() 2.设置要连接的对方IP地址和端口等属性 3.连接服务器,用函数connect()

Java TCP/UDP socket 编程流程总结

最近正好学习了一点用java socket编程的东西.感觉整体的流程虽然不是很繁琐,但是也值得好好总结一下. Socket Socket可以说是一种针对网络的抽象,应用通过它可以来针对网络读写数据.就像通过一个文件的file handler就可以都写数据到存储设备上一样.根据TCP协议和UDP协议的不同,在网络编程方面就有面向两个协议的不同socket,一个是面向字节流的一个是面向报文的. 对socket的本身组成倒是比较好理解.既然是应用通过socket通信,肯定就有一个服务器端和一个客户端.

关于网络编程中MTU TCP UDP优化设置总结

首先要看TCP/IP协议,涉及到四层:链路层,网络层,传输层,应用层.  其中以太网(Ethernet)的数据帧在链路层 IP包在网络层 TCP或UDP包在传输层 TCP或UDP中的数据(Data)在应用层 它们的关系是 数据帧{IP包{TCP或UDP包{Data}}} --------------------------------------------------------------------------------- 在应用程序中我们用到的Data的长度最大是多少,直接取决于底层的

Go语言TCP/UDP Socket编程

1. TCP编程 TCPClient // TCPClient project main.go package main import ( "fmt" "net" "os" ) func main() { var buf [512]byte if len(os.Args) != 2 { fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0]) os.Exit(1) }

TCP和UDP编程时区别

一.TCP与UDP的区别 基于连接与无连接 对系统资源的要求(TCP较多,UDP少) UDP程序结构较简单 流模式与数据报模式 TCP保证数据正确性,UDP可能丢包 TCP保证数据顺序,UDP不保证 部分满足以下几点要求时,应该采用UDP 面向数据报方式 网络数据大多为短消息 拥有大量Client 对数据安全性无特殊要求 网络负担非常重,但对响应速度要求高 具体编程时的区别 socket()的参数不同 UDP Server不需要调用listen和accept UDP收发数据用sendto/rec

Java 网络编程(二) 两类传输协议:TCP UDP

两类传输协议:TCP,UDP TCP TCP是Transfer Control Protocol(传输控制协议)的简称,是一种面向连接的保证可靠传输的协议. 在TCP/IP协议中, IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一确定Internet上的一台主机. 而TCP层则提供面向应用的可靠的或非可靠的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的. 通过TCP协议传输,得到的是一个顺序的无差错的数据流. 发送方和接收方的成对的两个socket之间