赞
踩
Socket的原意是“插座”,在计算机通信领域,socket 被翻译为“套接字”。
Socket通信主要有两个类型:TCP、UDP。
TCP通信,是一个有序的、可靠的、面向连接的通信方式。用数据流的方式传递信息。
UDP通信,是无连接的、不保证有序到达的、但具有较好的实时性、能够高速传输的通信方式。用数据报的方式传递信息。
其中,UDP Socket主要使用场景为:实时性要求高,可以接受一定的数据错误和丢失。例如在线游戏、音视频聊天等场景。
UDP Socket对于客户端和服务端的区分不明显,在需要时,客户端和服务端均可以使用connect、bind函数。
因此,本文从对我个人来说更好理解的角度定义客户端和服务端,即以我方服务和对方服务来区分两端。
在此介绍Linux系统下UDP Socket通信的基本流程,使用C语言实现。(Windows系统下另需要其他步骤如WSAStartup,请自行查找)
#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> // 未使用,提供设置及获取域名的函数
#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方便理解
SOCKET mySocket; // (我方)网络套接字
sockaddr_in mySockAddr; // 我方网络地址
sockaddr_in theySockAddr; // 对方网络地址
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; }
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对方物理地址长度
绑定我方套接字和我方服务地址
int ret = bind(mySocket, (sockaddr*)&mySockAddr, sizeof(mySockAddr)); // ret返回0成功绑定
把我方套接字连接至对方服务地址,连接后向对方服务发送数据使用send函数,否则使用sendto函数
int ret = connect(mySocket, (sockaddr*)&theySockAddr, sizeof(theySockAddr));
存储数组定义:
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: %s\n", 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);
close(mySocket);
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: %s\n", 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(๑•̀ㅂ•́)و✧)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。