套接字是网络通信的基石,是网络通信的基本构建,最初是由加利福利亚大学Berkeley分校为UNIX开发的网络通信编程接口,本文主要介绍套接字的有关内容,了解使用套接字编写程序过程
一.概念
所谓的套接字,实际上是一个指向传输提供者的句柄。根据性质和作用的不同,套接字可以分为原始套接字、流式套接字和数据包套接字3种
- 原始套接字:原始套接字是在WinSock2规范中提出的,它能够是程序开发人员对底层的网络传输机制进行控制,在原始套接字下接收到数据中包含IP头。
- 流式套接字:流式套接字提供了双向、有序、可靠的数据传输服务,该类型套接字在通信前需要双方建立连接。TCP协议采用的就是流式套接字
- 数据包套接字:与流式套接字对应的数据包套接字,数据包套接字提供了双向的数据流,但是它不能保证数据包传输的可靠性、有序性和无重复性。UDP协议采用的就是数据包套接字
1.TCP的套接字的socket编程
基于TCP面向连接的socket编程的服务器端程序流程如下:
① 创建套接字socket(socket
)
② 创建的套接字绑定(bind
)到本地的地址和端口上
③ 设置套接字的状态为监听(listen
),准备接受客户端的连接请求
④ 接受请求(accpet
),同时返回得到一个用于连接的新套接字
⑤ 使用这个新套接字进行通信(通信函数使用send
/recv
)
⑥ 通信完毕,释放套接字资源(close
)
基于TCP面向连接的socket编程的客户端程序流程如下:
① 创建套接字socket(socket
)
② 向服务器发出连接请求(connect
)
③ 请求连接后与服务器进行通信操作(send
/recv
)
④ 释放套接字资源(close
)
注意:
在服务器的一端,当调用了accept
函数时,程序就会进行等待,直到有客户端调用connect
函数发送连接请求,然后服务器接受该请求,这样服务器与客户端就建立了连接;
在服务器端要建立套接字绑定到指定的主机IP和端口上等待客户的请求,但是对于客户端来说,当发起连接请求并被接受后,在服务器端就保存了该客户端的IP地址和端口号的信息。对于服务器端来说,一旦建立连接之后,实际上它已经保存了客户端的IP和端口号的信息了,因此可以利用返回的套接字进行与客户端的通信
2.UDP的套接字的socket编程
基于UDP面向无连接的socket编程接收端程序如下:
① 创建套接字socket (socket
)
② 将套接字绑定(bind
)到一个本地地址和端口上
③ 等待接收数据(recvfrom
)
④ 释放套接字资源(close
)
基于UDP面向无连接的socket编程发送端程序如下:
① 创建套接字socket(socket
)
② 向服务器发送数据(sendto
)
③ 释放套接字资源(close
)
二.通信函数
头文件导入#include
#include
1.socket函数
函数:int socket(int, int, int);
功能:创建一个套接字
解释:int socket(int af, int type, int protocol);
- af:表示一个地址家族,IPv4连接时填
AF_INET
,IPv6连接时对应AF_INET6
- type:表示套接字类型,如果是
SOCK_STREAM
表示创建面向连接的流式套接字;如果是SOCK_DGRAM
表示面向无连接的数据包套接字;为SOCK_RAW
表示创建原始套接字 - protocol:表示套接口所用的协议,如果用户不指定,可以设置为0
- 返回值:int类型的一个指向传输提供者的句柄(套接字)
示例:int socket = socket(AF_INIT, SOCK_STREAM,0);
创建一个对应IPv4的TCP套接字
2.bind函数
函数:int bind(int, const struct sockaddr *, socklen_t)
功能:将套接字绑定到指定端口和地址上
解释:int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen);
sockfd:表示套接字标识
-
my_addr:是一个指向
struct sockaddr
结构体类型的指针,一般绑定 IPv4地址传入struct sockaddr_in
指针,IPv6地址传入struct sockaddr_in6
指针(PS:这里我也不是很清楚,sockaddr_in
结构体里面有地址族,改变地址族为IPv6的不知道行不行,下面的演示和注释以前面的struct sockaddr_in
结构体为主),都包含地址和端口号信息,强转成需要的类型,结构体具体参数如下
//示例: struct sockaddr_in addrs; addrs.sin_len = sizeof(addrs); addrs.sin_family = AF_INET; addrs.sin_port = htons(5000); addrs.sin_addr.s_addr = htonl(INADDR_ANY);
addrlen:确定my_addr缓冲区的长度,就是前面结构体的大小
返回值:如果函数执行成功,则返回值为0,失败则为-1
示例:int result = bind(socket, (struct sockaddr *)&addrs,sizeof(addrs));
将套接字绑定到主机地址,端口号为5000的端口上
3.listen函数
函数:int listen(int, int)
功能:将套接字设置为监听模式,对于流式套接字,必须处入监听模式才能够接收客户端套接字的连接
解释:int listen(int sockfd, int backlog);
- sockfd:表示套接字标识
- backlog:表示等待连接的最大队列长度。例如:如果backlog被设置为2,此时有3个客户端同时发出连接请求,那么前两个客户端会放置在等待连接队列中,第三个客户端会得到错误信息。
- 返回值:成功则返回0,失败则返回-1
示例:listen(socket, 5);
设置套接字为监听状态,为连接做准备,最大等待的连接缓冲为5
4.accept函数
函数:int accept(int, struct sockaddr * __restrict, socklen_t * __restrict)
功能:接受客户端的连接,在流式套接字中,只有在套接字处入监听状态,才能接受客户端的连接
解释:int accept(int sockfd, struct sockaddr *addr, int *addrlen);
- sockfd:表示套接字标识
- addr:是一个sockaddr_in结构体指针,接收连接端地址信息
- addrlen:前面sockaddr_in结构体地址长度指针,用来接收连接端地址信息结构体长度
- 返回值:一个新的套接字,它对应于已经接受的客户端连接,对于该客户端的所有后续操作,都应使用这个新的套接字
示例:int socket_server = accept(socket, (struct scokaddr*)&client_addr,&addrlen);
socket_server保存接受请求后返回的新的套接字,socket_server为绑定在地址和端口上的套接字,而client_addr是有关连接端地址信息结构,最后的addrlen是client_addr的大小
5.close函数
函数:int close(int)
功能:关闭套接字
解释:int close(int sockfd);
6.connect函数
函数:int connect(int, const struct sockaddr *, socklen_t)
功能:发送一个连接请求
解释:int connect(int sockfd,const struct sockaddr * addr_server, int addrlen)
- sockfd:表示一个正准备连接的套接字。客户端连接套接字
int socket_client
- addr_server:存储套接字要连接主机的地址信息结构体指针
- addrlen:前面结构体的也就是
addr_server
结构体缓冲区的长度 - 返回值:成功则返回0,失败则返回-1
示例:int result = connect(socket_client, (struct sockaddr *)&addr_server, sizeof(addr_server));
7.recv函数
函数:ssize_t recv(int, void *, size_t, int)
功能:从流式套接字中接受数据,平时开发针对TCP套接字接收数据
解释:
ssize_t recv(
int sockfd, //表示流式套接字
void *buff, //用来存放recv函数接收到的数据的缓冲区
size_t nbytes, //缓冲区的长度
int flags //表示函数的调用方式,一般填0,具体看下面表格
)
//成功时返回接收的字节数(收到EOF是返回0),失败是返回-1
示例:ssize_t recvLen = recv(socket, receivebuf, 100, 0);
其中receivebuf是保存接收数据,后面的100是实际要改善接收数据的字节数
8.send函数
函数:ssize_t send(int, const void *, size_t, int)
功能:在流式套接字中发送数据
解释:
ssize_t send(
int sockfd, //表示流式套接字
const void *buff, //存放要发送数据的缓冲区
size_t nbytes, //实际要改善的数据的字节数
int flags //表示函数的调用方式,一般填0,具体看下面表格
)
//返回值:成功返回发送的字节数,错误返回-1
示例:ssize_t sendLen = send(socket, sendbuf,100,0);
其中sendbuf是保存要发送数据组
9.recvfrom函数
函数:ssize_t recvfrom(int, void *, size_t, int, struct sockaddr * __restrict, socklen_t * __restrict)
功能:用于接收一个数据包,并保存源地址
解释:
ssize_t recvfrom(
int sockfd, //表示准备接收的套接字
void * buff, //指向缓冲区的指针,用来接收数据
size_t nbytes, //表示缓冲区的长度
int flags, //表示调用方式,一般填0
struct sockaddr * __restrict from, /* 是一个指向地址结构体的指针,用来接收发送数据方的地址信息 */
socklen_t * __restrict fromLen //前面结构体长度指针
)
//返回值:如果正确接收返回接收到的字节数,失败返回-1.
10.sendto函数
函数:ssize_t sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t)
功能:向指定的目的地方发送数据
解释:
ssize_t sendto(
int sockfd, //表示准备发送数据的套接字
const void * buff, //指向缓冲区的指针,该缓冲区包含将要发送的数据
size_t nbytes, //缓冲区数据长度
int flags, //表示调用方式,一般填0
const struct sockaddr * to, //指向目标套接字地址的结构体指针
socklen_t tolen, //前面结构体长度
)
//成功则返回实际传送出去的字符数,失败返回-1,错误原因存于errno 中。
linux版本flag参数:
flags | 说明 | recv | send |
---|---|---|---|
MSG_OOB | 用于传输外带数据 | ✅ | ✅ |
MSG_PEEK | 窥看外来消息(验证数据缓冲中是否存在接收的数据) | ✅ | ❎ |
MSG_DONTROUTE | 绕过路由表查找(数据传输过程中不参考路由表, 在本地网络中寻找目的地) | ❎ | ✅ |
MSG_DONTWAIT | 调用I/O函数是不阻塞, 用于使用非阻塞I/O | ✅ | ✅ |
MSG_WAITALL | 等待所有数据(防止函数返回,直到接收全部请求的字节数) | ✅ | ❎ |
三.辅助函数
1.htons() 函数 和 ntohs()函数
函数:u_short htons(u_short hostshort)
功能:将一个16位的无符号端整形数据由主机排列方式转换成网络排列方式,所谓的网络排列方式就是大端排列方式,MacOS是采用小端的存储方式存储数据
使用地方:在有关主机地址和端口号结构体中struct sockaddr_in
里面,属性in_port_t sin_port
表示端口号,因为端口号要用网络排列方式,使用该函数转换后赋值
函数: u_short ntohs(u_short netshort)
功能: 与htons()
功能相反,将16位无符网络排列端口转换成主机排列方式,也就是将16位大端排列数字转换成小端排列方式
使用地方: 得到地址结构体struct sockaddr_in
,将里面的in_port_t sin_port
转换成我们平时看到的小端排列的端口号
2.htonl() 函数 和 ntohl()函数
函数:u_long htonl(u_long hostlong)
功能:将一个32位无符号整形由主机排列方式转换成网络排列方式,所谓的网络排列方式就是大端排列方式
使用地方:在有关主机地址和端口号结构体中struct sockaddr_in
里面,结构体属性struct in_addr sin_addr
中的in_addr_t s_addr
属性表示IP地址信息,因为IP地址信息要用网络排列方式,使用该函数转换后赋值
//1.申明一个字符串IP地址
NSString *ip = @"192.168.1.1";
NSArray *ip_arr = [ip componentsSeparatedByString:@"."];
//2.将IP地址转换成32位4字节的无符整形数据
uint smart_ip = ([ip_arr[0] intValue] << 24) | ([ip_arr[1] intValue] << 16) | ([ip_arr[2] intValue] << 8) | [ip_arr[3] intValue];
NSLog(@"%u",smart_ip);
/* ① 打印出来smart_ip: 3232235777 -> 0xC0A80101
* ② 在Xcode里面查看smart_ip内存:0x7ffeefbff5cc : 01 01 A8 C0
* 将指针指向的地址分成内存编号如下
* 0x7ffeefbff5cc -> 01
* 0x7ffeefbff5cd -> 01
* 0x7ffeefbff5ce -> A8
* 0x7ffeefbff5cf -> C0
* 从上面可以看出在内存中,数据是小端存储方式(数据的高位存高位),
* 所以说要是直接传入数字对面会解析成 1.1.168.192,这样地址接反过来了,所以需要转换
*/
//3.调用函数转换数据的排列方式
uint big_ip = htonl(smart_ip);
NSLog(@"%u",big_ip);
/* ① 打印出来smart_ip: 16885952 -> 0x0101A8C0
* ② 在Xcode里面查看smart_ip内存:0x7ffeefbff5c8 : C0 A8 01 01
* 将指针指向的地址分成内存编号如下
* 0x7ffeefbff5c8 -> C0
* 0x7ffeefbff5c9 -> A8
* 0x7ffeefbff5ca -> 01
* 0x7ffeefbff5cb -> 01
* 所以传入 16885952 对面会按大端解析成 192.168.1.1
*/
//我们知道在局部变量是在函数执行时分配到栈上的,栈是由高地址位向低址位扩展的,函数执行时"先提"升栈(这里的"提升"是说栈顶指针向低址位扩展),为函数执行在栈上分配缓存的空间,smart_ip是前面的参数,所以写进栈里面在较高的内存地址位,而big_ip是后面的参数,后写进栈里面,在低址位,所以要比smart_ip内存地址低
函数:u_long ntohl(u_long netlong)
功能:与函数 htonl()
功能相反,将网络排列的32位无符数据转换成主机排列,就是将32位大端排列数字转换成小段排列数据
使用地方:得到地址结构体struct sockaddr_in
,将里面的in_addr_t s_addr
转换成我们平时看到的小端排列32位IP地址
3.inet_addr 函数
函数:in_addr_t inet_addr(const char *)
功能:将存储IP地址的char字符串转换成网络排列方式的32位无符号整形,跟上面htonl()函数功能一样
使用地方:转换的结果可直接用来给地址信息结构体里面的IP地址赋值,因为转换出来的结果是网络排列的
//声明地址信息结构体
struct sockaddr_in addr;
NSString *ip = @"192.168.1.60";
const char *cChar_ip = [ip cStringUsingEncoding:NSUTF8StringEncoding]; //转换的结果就是 "192.168.1.60"
in_addr_t addr_ip = inet_addr(cChar_ip);
addr.sin_addr.s_addr = addr_ip; //结构体里面的IP地址赋值
4.inet_aton 函数
函数:int inet_aton(const char *, struct in_addr *);
功能:与函数inet_addr
功能一样,将char字符串IP地址转换成网络排序的无符整形,传入struct in_addr
结构体指针,直接赋给结构体
使用地方:
struct sockaddr_in addr;
NSString *ip = @"192.168.1.60";
const char *cChar_ip = [ip cStringUsingEncoding:NSUTF8StringEncoding];
int result = inet_aton(cChar_ip, &addr.sin_addr);
if (result != 0) {
printf("地址赋值成功\n");
}else {
printf("地址赋值无效\n");
}
//函数返回非0表示cp主机有地有效,返回0表示主机地址无效
5.inet_pton 函数
函数:int inet_pton(int, const char *, void *);
功能:与辅助函数htonl()
、inet_addr()
、inet_aton()
的功能一样,将char字符串IP地址转换成网络排序的无符整形,直接赋给struct in_addr
结构体指针里面,不一样的是可以根据地址族的不同转换IPv6还是IPv4的地址
解释:
int inet_pton(
int af, //地址族 AF_INET 对应IPv4,AF_INET6对应IPv6
const char *src, //盛装C字符串格式的IP地址信息
void *dst //地址结构体里面 struct in_addr IP地址结构指针
);
//返回值:若成功则为1,若输入不是有效的IP地址表达式则为0,若出错则为-1
示例:
struct sockaddr_in addr;
NSString *ip = @"192.168.1.60";
const char *cChar_ip = [ip cStringUsingEncoding:NSUTF8StringEncoding];
int result = inet_pton(AF_INET, cChar_ip, &addr.sin_addr);
if (result == 1) {
printf("地址赋值成功\n");
}else if (result == 0) {
printf("输入的地址无效\n");
}else {
printf("转换出错\n");
}
6.inet_ntoa 函数
函数:char *inet_ntoa(struct in_addr)
功能:正好与上面的函数inet_aton
功能相反,需要传入一个关于地址信息的结构体,解析出来C字符串的IP地址
使用地方及方式:
//接着辅助函数3/4/5的例子往下走
char *cChar_ip_out= inet_ntoa(addr.sin_addr);
NSLog(@"%s",cChar_ip_out);
NSLog(@"%@",[NSString stringWithCString:cChar_ip_out encoding:NSUTF8StringEncoding]);
/* 打印结果:
192.168.1.60
192.168.1.60
*/
所以这样从地址结构体里面获取IP地址,就可以用该函数直接可以将结构体里面的struct in_addr
IP地址结构体转换成c字符串进行输出和打印
7.inet_ntop 函数
函数:const char *inet_ntop(int, const void *, char *, socklen_t);
功能:跟上面函数inet_ntoa()
功能相似,于函数inet_pton()
功能相反,不一样的是他可以传入地址族,传入AF_INET
则解析出IPv4的地址,传入AF_INET6
则解析出IPv6的地址
解释:
const char *inet_ntop(
int af, //地址族 AF_INET 对应IPv4,AF_INET6对应IPv6
const void * src, //地址结构体里面 struct in_addr IP地址结构指针
char * dst, //盛装C字符串格式的IP地址信息
socklen_t cnt //C字符串的宽度
)
//返回值:若成功则为指向C字符串格式IP地址缓存的指针,若出错则为NULL
示例:
//接着辅助函数3、4、5的例子往下走
char cChar_ip_out[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &addr.sin_addr, cChar_ip_out, (socklen_t)sizeof((cChar_ip_out))) == NULL) {
printf("地址转换出错\n");
}else {
printf("地址转换成功:%s \n",cChar_ip_out);
}
8.getpeername 函数
函数:int getpeername(int, struct sockaddr * __restrict, socklen_t * __restrict)
功能:获取socket套接字对方的地址信息,返回0时正常,否则错误
解释:int getpeername(int sockfd, struct sockaddr * peerAddr, socklen_t * addrLen)
- sockfd:表示套接字
- peerAddr:接收地址信息结构体,传入结构体指针外面申明里面赋值,获取到的是连接对象的地址信息
- addrLen:地址信息结构体的长度指针
- 返回值:返回0时可以通过地址信息指针取里面的信息,<0时错误
使用方式:
//IPv4连接,获取对方的IP地址
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getpeername(sockfd, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
{
NSLog(@"获取地址出错");
}else {
char addrBuf[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &sockaddr4.sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
{
addrBuf[0] = '\0';//表示字符结束
}
NSString *ip = [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
NSLog(@"获取的IPv4地址为:%@",ip);
}
//IPv6连接,获取对方的IP地址
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getpeername(sockfd, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
{
NSLog(@"获取地址出错");
}else {
char addrBuf[INET6_ADDRSTRLEN];
if (inet_ntop(AF_INET6, &sockaddr6.sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
{
addrBuf[0] = '\0';//表示字符结束
}
NSString *ip = [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
NSLog(@"获取的IPv6地址为:%@",ip);
}
8.getsockname 函数
函数:int getsockname(int, struct sockaddr * __restrict, socklen_t * __restrict)
功能:获取socket套接字的地址信息,返回0时正常,否则错误
解释:int getsockname(int sockfd, struct sockaddr * addr, socklen_t *addrLen)
- sockfd:表示套接字
- addr:接收地址信息结构体,传入结构体指针外面申明里面赋值,获取到的是sockfd的地址信息
- addrLen:地址信息结构体的长度指针
- 返回值:返回0时可以通过地址信息指针取里面的信息,<0时错误
使用方式
//IPv4连接,获取socket的IP地址
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getsockname(sockfd, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
{
NSLog(@"获取地址出错");
}else
{
char addrBuf[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &sockaddr4.sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
{
addrBuf[0] = '\0';//表示字符结束
}
NSString *ip = [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
NSLog(@"获取socket的IPv4地址为:%@",ip);
}
//IPv6连接,获取socket的IP地址
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getsockname(sockfd, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
{
NSLog(@"获取地址出错");
}else
{
char addrBuf[INET6_ADDRSTRLEN];
if (inet_ntop(AF_INET6, &sockaddr6.sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
{
addrBuf[0] = '\0';
}
NSString *ip = [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
NSLog(@"获取socket的IPv6地址为:%@",ip);
}
四.简单的代码实现
下面是关于套接字简单的实现,只做参考,可以进行简单的通信但是不能用作正式的项目开发(因为里面有太多的逻辑没做判断和参数设置),下面代码编写是基于Xcode Command Line Tool
工程
- TCP服务器和客户端代码实现
#include
#include
#include
#include
#include
int creatServer(void);
int creatClient(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int serv_result = creatServer();
if (serv_result == 0) printf("开启服务器失败\n");
//int clnt_result = creatClient();
//if (clnt_result == 0) printf("开启客户端失败\n");
}
return 0;
}
#pragma mark ---服务器端套接字创建并开启监听
int creatServer()
{
int serv_sockfd, clnt_sockfd;
struct sockaddr_in serv_addr,clnt_addr;
serv_addr.sin_len = sizeof(struct sockaddr_in);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(2000);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (bind(serv_sockfd, (struct sockaddr*)&serv_addr, serv_addr.sin_len) < 0) return 0;
if (listen(serv_sockfd, 1) < 0) return 0;
socklen_t len_t = sizeof(struct sockaddr_in);
clnt_addr.sin_len = len_t;
clnt_sockfd = accept(serv_sockfd, (struct sockaddr*)&clnt_addr, &len_t);
if (clnt_sockfd < 0) return 0;
close(serv_sockfd);
printf("有客户端连接 IP:%s Port:%d\n",inet_ntoa(clnt_addr.sin_addr),ntohs(clnt_addr.sin_port));
char recv_buffer[512];
while (1) {
memset(recv_buffer, 0, 512);
ssize_t recv_len = recv(clnt_sockfd, recv_buffer, sizeof(recv_buffer), 0);
if (recv_len == 0) {
printf("通信结束\n");
break;
}else if (recv_len < 0) {
printf("读取数据出错\n");
break;
}else {
printf("recv: %s\n",recv_buffer);
}
}
printf("服务器关闭\n");
close(clnt_sockfd);
return 1;
}
#pragma mark ---客户端端套接字创建并开始连接
int creatClient()
{
printf("请输入服务器IP地址:\n");
char ip[INET_ADDRSTRLEN];
scanf("%s",ip);
int clnt_sockfd;
clnt_sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in clnt_addr;
clnt_addr.sin_len = sizeof(struct sockaddr_in);
clnt_addr.sin_family = AF_INET;
clnt_addr.sin_port = htons(2000);
inet_aton(ip, &clnt_addr.sin_addr);
if (connect(clnt_sockfd, (struct sockaddr*)&clnt_addr, clnt_addr.sin_len) < 0) return 0;
printf("连接成功: %s 输入 'C' 退出结束\n",ip);
char send_buffer[512];
while (1) {
memset(send_buffer, 0, 512);
printf("请输入需要发送的内容:");
gets(send_buffer);
size_t buffer_len = strlen(send_buffer);
if (buffer_len == 0) continue;
if (strcmp(send_buffer, "C") == 0) break;
ssize_t send_len = send(clnt_sockfd, send_buffer, buffer_len, 0);
if (send_len == buffer_len) {
printf("发送成功: %zu %zd\n",buffer_len,send_len);
}else {
printf("发送失败\n");
break;
}
}
printf("客户端关闭\n");
close(clnt_sockfd);
return 1;
}
- UDP发送端和接收端实现
#include
#include
#include
#include
#include
#include
int sendPacket(void);
int recvPacket(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int send_result = sendPacket();
if (send_result == 0) printf("开启发送失败\n");
//int recv_result = recvPacket();
//if (recv_result == 0) printf("开启接收失败\n");
}
return 0;
}
#pragma mark ---UDP数据发送端
int sendPacket()
{
printf("请输入接收方IP地址:\n");
char ip[INET_ADDRSTRLEN];
scanf("%s",ip);
int send_sock;
send_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (send_sock < 0) return 0;
struct sockaddr_in addr;
addr.sin_len = sizeof(struct sockaddr_in);
addr.sin_family = AF_INET;
addr.sin_port = htons(2001);
inet_aton(ip, &addr.sin_addr);
printf("准备完毕,可以开始通信,输入字符'C'停止发送\n");
char send_buffer[512];
while (1) {
memset(send_buffer, 0, 512);
printf("请输入要发送的内容:");
gets(send_buffer);
size_t buffer_len = strlen(send_buffer);
if (buffer_len == 0) continue;
ssize_t send_len = sendto(send_sock,
&send_buffer,
buffer_len,
0,
(struct sockaddr*)&addr,
addr.sin_len);
if (send_len == buffer_len) {
printf("UDP数据包发送成功\n");
}else {
printf("UDP数据包发送失败: %s\n",strerror(errno));
break;
}
if (strcmp(send_buffer, "C") == 0) break;
}
printf("关闭UDP套接字\n");
close(send_sock);
return 1;
}
#pragma mark ---UDP数据接收端
int recvPacket()
{
int recv_sock;
recv_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (recv_sock < 0) return 0;
struct sockaddr_in addr,recv_addr;
socklen_t len = sizeof(struct sockaddr_in);
addr.sin_family = AF_INET;
addr.sin_len = sizeof(struct sockaddr_in);
addr.sin_port = htons(2001);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(recv_sock, (struct sockaddr*)&addr, addr.sin_len) < 0) {
printf("%s\n",strerror(errno));
return 0;
}
printf("开始接收UDP数据包,收到字符'C'停止接收\n");
char recv_buffer[512];
while (1) {
memset(recv_buffer, 0, 512);
ssize_t recv_len = recvfrom(recv_sock,
recv_buffer,
sizeof(recv_buffer),
0,
(struct sockaddr*)&recv_addr,
&len);
if (recv_len <= 0) {
printf("接收UDP数据包失败:%s\n",strerror(errno));
break;
}
printf("recv: %s\n",recv_buffer);
if (strcmp(recv_buffer, "C") == 0) break;
}
printf("关闭UDP套接字\n");
close(recv_sock);
return 1;
}
五.尾声
接下来的文章会继续更新有关套接字的详解,这是我起初学习的一个流程,希望也对大家有帮助!