【Socket】Linux下UDP Socket的基本流程以及connect、bind函数的使用(C语言实现)


【Socket】Linux下UDP Socket中connect、bind函数的使用(C语言实现)


一、UDP Socket简介

Socket的原意是“插座”,在计算机通信领域,socket 被翻译为“套接字”。

Socket通信主要有两个类型:TCP、UDP。

TCP通信,是一个有序的、可靠的、面向连接的通信方式。用数据流的方式传递信息。
UDP通信,是无连接的、不保证有序到达的、但具有较好的实时性、能够高速传输的通信方式。用数据报的方式传递信息。

其中,UDP Socket主要使用场景为:实时性要求高,可以接受一定的数据错误和丢失。例如在线游戏、音视频聊天等场景。

UDP Socket对于客户端和服务端的区分不明显,在需要时,客户端和服务端均可以使用connect、bind函数。
因此,本文从对我个人来说更好理解的角度定义客户端和服务端,即以我方服务和对方服务来区分两端。

二、Linux下socket的基本流程

在此介绍Linux系统下UDP Socket通信的基本流程,使用C语言实现。(Windows系统下另需要其他步骤如WSAStartup,请自行查找)

1、头文件引用

#include <stdio.h>
#include <string.h>
#include <unistd.h> // 提供通用的文件、目录、程序及进程操作的函数,如close()
#include <sys/socket.h> // 提供socket主要函数
#include <arpa/inet.h> // 提供IP地址格式转换函数;包含了<netinet/in.h>,提供sockaddr_in数据结构
//#include <netdb.h> // 未使用,提供设置及获取域名的函数

2、宏定义部分

#define MY_PORT 2024 // 我方服务PORT
#define THEY_IP "192.168.0.88" // 对方服务IP
#define THEY_PORT 2023 // 对方服务PORT
#define SOCKET int // Linux下的socket()函数返回int类型,这里定义SOCKET方便理解

3、声明全局变量

SOCKET mySocket; // (我方)网络套接字
sockaddr_in mySockAddr; // 我方网络地址
sockaddr_in theySockAddr; // 对方网络地址

4、定义和配置Socket

bool init() {
	// 定义(我方)网络套接字
	mySocket = socket(AF_INET, SOCK_DGRAM, 0);

	// 定义我方物理地址配置
	mySockAddr.sin_family = AF_INET;
	mySockAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有地址
	mySockAddr.sin_port = htons(MY_PORT);

	// 定义对方物理地址配置
	theySockAddr.sin_family = AF_INET;
	theySockAddr.sin_addr.s_addr = inet_addr(THEY_IP);
	theySockAddr.sin_port = htons(THEY_PORT);

	return true;
}

5、connect和bind函数介绍

bind()函数一般在服务器端使用,表示绑定我方服务socket的IP和端口。

int bind(SOCKET socket, const struct sockaddr * addr, socklen_t len)
// socket套接字,addr我方物理地址,len我方物理地址长度

connect()函数一般在客户端使用,表示把我方服务socket连接至对方服务IP和端口,是面向连接的。主要作用在后续send与sendto函数的使用区别。使send更加方便,就不需要每次使用sendto指定目标服务了。

int connect(SOCKET socket, const struct sockaddr * addr, socklen_t len)
// socket套接字,addr对方物理地址,len对方物理地址长度

6、套接字绑定

绑定我方套接字和我方服务地址

int ret = bind(mySocket, (sockaddr*)&mySockAddr, sizeof(mySockAddr)); // ret返回0成功绑定

7、(可选)连接至对方服务

把我方套接字连接至对方服务地址,连接后向对方服务发送数据使用send函数,否则使用sendto函数

int ret = connect(mySocket, (sockaddr*)&theySockAddr, sizeof(theySockAddr)); 

8、收发数据

存储数组定义:

char recvBuf[512]; // 接收信息存储的数组地址
char sendBuf[512]; // 发送信息存储的数组地址
char tempBuf[512]; // 中间数据

接收数据:

sockaddr_in otherSockAddr; // 定义对方服务网络地址,这个地址不只表示第三节说的对方网络地址,也包括其他服务的地址,这里作为存储单元使用
socklen_t sockLen = sizeof(otherSockAddr);

int recvLen = recvfrom(mySocket, recvBuf, 512, 0, (sockaddr*)&otherSockAddr, &sockLen); // 返回接收数据长度,长度>0认为有数据

printf("From: %s:%d.nMessage: %sn", inet_ntoa(otherSockAddr.sin_addr), ntohs(otherSockAddr.sin_port), recvBuf); // 获得对方服务的ip/port

发送数据:

// 可以接收了谁的数据返回给谁,也可以固定一个目标服务,以前者为例
strcpy(sendBuf, "GOT IT!");
sendto(mySocket, sendBuf, 512, 0, (sockaddr*)&otherSockAddr, sockLen); // 通过我的套接字发送给对方服务

// connect时使用:仅发送给连接的对象
send(mySocket, sendBuf, 512, 0);

9、关闭套接字

close(mySocket);

10、完整的主函数

int main() {
	// 1、初始配置
	if (!init()) {
		printf("INIT ERROR!n");
		return -1;
	}

	// 2、我方服务套接字绑定
	int ret = bind(mySocket, (sockaddr*)&mySockAddr, sizeof(mySockAddr));
	if (ret == 0) {
		printf("UDP SERVER IS START AND BIND TO %d.n", MY_PORT);
	}
	else {
		printf("PORT %d BIND FAILED!n", MY_PORT);
		return -1;
	}
	
	// (可选)连接至对方服务
	//int ret = connect(mySocket, (sockaddr*)&theySockAddr, sizeof(theySockAddr)); 
	
	// 3、定义存储地址
	char recvBuf[512]; // 接收信息存储的数组地址
	char sendBuf[512]; // 发送信息存储的数组地址
	char tempBuf[512]; // 中间数据

	// 4、定义接收到的其他服务地址
	sockaddr_in otherSockAddr;
	socklen_t sockLen = sizeof(otherSockAddr);
	
	// 持续收发
	while (true) {
		// 5、接收
		int recvLen = recvfrom(mySocket, recvBuf, 512, 0, (sockaddr*)&otherSockAddr, &sockLen); // 返回接收数据长度,长度>0认为有数据
		printf("From: %s:%d.nMessage: %sn", inet_ntoa(otherSockAddr.sin_addr), ntohs(otherSockAddr.sin_port), recvBuf); // 打印接收到的对方服务的ip/port
		if (!strcmp(recvBuf, "exit")) { // 接收到exit退出收发(这个方式退出不安全)
			printf("Exit success.n");
			break;
		}
		
		// 6、发送
		strcpy(sendBuf, "GOT IT!");
		sendto(mySocket, sendBuf, 512, 0, (sockaddr*)&otherSockAddr, sockLen); 	
		// connect时使用:仅发送给连接的对象
		//send(mySocket, sendBuf, 512, 0);
	}

	// 7、关闭套接字
	close(mySocket);
	return 0;
}

(代码没有经过测试,相信各位都是成熟的程序员,会自己改bug(๑•̀ㅂ•́)و✧)