学习了socket套接字之后,我试着根据相关接口搭载了一个自己的小型UDP服务器。在这里做一下总结,记录搭载过程。
一、什么是socket(套接字)?
所谓套接字,即是指在建立链接过程中的“套接”行为。想实现两端的互联,就首先要唯一的标识出来对方,套接字的作用就是唯一的确定一台主机上的一个进程。如果客户端想要对服务器“套接”,就需要知道知道服务器的ip地址,当然,这只能标识服务器的主机;还需要知道负责与你“套接”的进程,即是端口号(port)。只有先这样“套接”之后,才具备了互联,传输的前提。
PS:为什么不直接用进程ID标识进程呢?
这里我是这么理解的,进程ID是由操作系统内核分配管理的,我们没通信之前,我怎么知道你那的进程ID是什么?所以我们约定好一个端口来标识我们负责套接的进程,这样就很方便了。
二、UDP通信的特点
特点:无链接,面向数据报(安全性不高,实时性高的场景:视频通话)传输速度快,无粘包。
缺点:不可靠
UDP建立连接的几个步骤:
服务器端: 客户端:
1.创建套接字 socket 1.创建套接字
2.为套接字绑定地址信息 bind 2.绑定地址 (不推荐主动绑定,有操作系统自动完成,反正我不做这一步)
3.接受/发送数据 recvfrom sendto 3.发送或接收数据
4.关闭套接字 close 4.关闭套接字
三、socket常用的API
这些都是大佬们给我们封装好的,再不会用就说不过去了。
int socket(int domain, int type, int protocol);
//domain:地址域 AF_INET代表IPV4网络协议
//type:套接字类型
// SOCK_STREAM 流式套接字 默认tcp
// SOCK_DGRAM 数据报套接字 默认udp
//protocol:协议 IPPROTO_UDP IPPROTO_TCP
//返回值:套接字描述符(非负整数) 失败:-1
int bind(int sockfd, struct sockaddr *addr,socklen_t addrlen);
// sockfd: 套接字描述符
// addr: 地址信息
// 使用sockaddr_in定义,使用时进行类型强转
// addrlen: 地址信息长度
// 返回值:0 失败:-1
struct sockaddr_in addr;//绑定地址信息
addr.sin_family = AF_INET;//地址域h使用ipv4
//uint16_t htons(uint16_t hostshort);
//将uint16_t类型的数据从主机字节序转换为网络字节序
addr.sin_port = htons(9000);
//uint32_t htonl(uint32_t hostlong);
//将uint32_t类型的数据从主机字节序转换为网络字节序
//in_addr_t inet_addr(const char *cp);
//将点分十进制字符串ip地址转换为网络字节序ip地址
addr.sin_addr.s_addr = inet_addr("192.168.122.135");
//addr.sin_addr.s_addr = htonl(0xc0a87a87);
socklen_t len = sizeof(struct sockaddr_in);
//上面做都得都是把这 struct sockaddr_in 这个结构体的信息都填进去。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
// sockfd: 套接字描述符---确定从哪里获取数据
// buf: 用于接收数据
// len: 接收的数据长度
// flags: 默认赋0---阻塞接收(没数据就一直等待)
// src_addr: 数据从哪里来,对端地址信息
// addrlen: 地址信息长度(输入输出复合参数)
// 返回值:实际接收的数据长度 失败:-1
注意:这里有个坑!!! 最后一个地址信息长度,需要取地址。凡是接收类的都需要对地址信息取地址!! 如下 &len
recvfrom(sockfd, buf, 1023, 0, (struct sockaddr*)&cli_addr, &len);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,struct sockaddr *dest_addr, socklen_t addrlen);
// dest_addr: 目的端地址信息
// addrlen: 地址信息长度
int close(sockfd);
这里有很重要的一个结构体,用来存放端口号,ip地址,地址域。模型如下
下面贴上我写好的代码,有一些坑会在代码中注释。
/*
*一个基于udp协议的网络通信服务端
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
//创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd < 0) {
perror("socket error");
return -1;
}
//把需要的信息都填到sockaddr_in这个结构体里,再使用绑定接口
struct sockaddr_in addr;//绑定地址信息
addr.sin_family = AF_INET;//地址域h使用ipv4
addr.sin_port = htons(9420);
addr.sin_addr.s_addr = inet_addr("172.20.10.3");
socklen_t len = sizeof(struct sockaddr_in);
int ret;
//这个地方要强转为(struct sockaddr*)类型
ret = bind(sockfd, (struct sockaddr*)&addr, len);
if (ret < 0) {
perror("bind error\n");
return -1;
}
while(1) {
//3. 接收数据
char buff[1024] = {0};
struct sockaddr_in cli_addr;
recvfrom(sockfd, buff, 1023, 0,
(struct sockaddr*)&cli_addr, &len);//len要取地址
printf("client say: %s\n", buff);
//4. 发送数据
memset(buff, 0x00, 1024);
scanf("%s", buff);
sendto(sockfd, buff, strlen(buff), 0,
(struct sockaddr*)&cli_addr,len);
}
//5. 关闭套接字
close(sockfd);
return 0;
}
/*
*一个基于udp协议的网络通信客户端
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<sys/socket.h>
#include<netinet/in.h>
int main()
{
int sockfd = 0;
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0)
{
printf("cli socket create failed\n");
return -1;
}
struct sockaddr_in ser_addr;
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(9420);
ser_addr.sin_addr.s_addr = inet_addr("172.20.10.3");
socklen_t addrlen = sizeof(struct sockaddr_in);
//被坑了一次才知道,客户端不要绑定,一绑定就出错
//int ret = 0;
//ret = bind(sockfd, (struct sockaddr*)&ser_addr, addrlen);
//if(ret < 0)
//{
// printf("cli bind failed\n");
// return -1;
//}
while(1)
{
char buf[1024] = {0};
scanf("%s",buf);
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&ser_addr, addrlen);
memset(buf, 0x00, 1024);
char buf2[1024] = {0};
recvfrom(sockfd, buf2, 1023, 0, (struct sockaddr*)&ser_addr, &addrlen);
printf("ser:%s\n",buf2);
}
close(sockfd);
return 0;
}
我这个UDP服务器涉及的知识比较简单浅显,知识把基本的接口用了一遍。但是在后面的TCP服务器有一些复杂的地方了。