1.套接字编程
主要讲解的就是如何编写一个网络通信程序
1.网络通信的数据中都会包含一个完整的五元组: sip,sport,dip,dport,protocol(源端IP,源端端口,对端IP,对端端口,协议)
五元组完整的描述了数据从哪来,到哪去,用的是什么数据格式。
2.网络通信,通常讨论的是两个主机进程之间的通信:客户端&服务端
客户端网络通信程序:通常指的是用户使用的一端
服务端网络通信程序: 通常指的是网络应用提供商提供服务的一端程序
客户端永远都是首先发起请求的一端(因为服务端是不知道客户端地址的(动态地址分配技术--谁上网给谁分配地址)
但是网络应用服务提供商,开发的客户端程序中都写入了服务器端的地址和端口,因此客户端是知道服务端地址的。还有一种原因就是,只有客户发送了请求,服务端才能提供对应的服务
2.TCP和UDP协议
套接字:socket的翻译,通常表示的是系统提供给程序员实现网络通信的一套接口
因此套接字编程学习的其实就是套接字接口的使用,通过这套接口完成网络通信程序的开发我们所讲解的套接字编程,主要是两个协议的通信程序编写: 传输层的TCP和UDP协议
TCP协议和UDP协议的区别:
联系:都是传输层协议
tcp协议: 传输控制协议--提供的是面向连接,可靠,基于字节流的数据传输
面向连接:通信前先要确定双方是否具有数据收发的能力
可靠传输:通过大量的一些控制机制,保证数据能够安全(有序且完整,一致)到达对端
字节流:没有传输大小限制,传输比较灵活的一种传输方式
tcp适用于安全要求大于实时要求的场景,比如文件传输
udp协议: 用户数据报协议--提供的是无连接,不可靠,基于数据包的数据传输
无连接:需要建立连接,只要知道对方的地址,就可以直接发送数据
不可靠:只要数据发送出去了就行,不管是否能够到达对端
数据报:有最大大小限制,且传输交付有大小限制的一种传输方式
因为没有大量的控制机制,因此传输速度快,因此适用于实时性要求大于安全性要求的场景,比如视频传输,音频传输
3.UDP协议
UDP协议通信程序的编写
UDP通信两端流程:
A.操作接口
创建套接字
int socket(int domain, int type, int protocol);
2.为套接字绑定地址信息
int bind(int sockfd, struct sockaddr*addr, socklen_len);
sockfd : socket返回的套接字描述符
addr: 要绑定的地址信息(不同地址域类型,有不同的地址结构)
因此IPv4通信定义 struct socketadddr_in结构即可
进行bind的时候,进行类型强转,这样可以实现一个接口绑定多种不同的地址结构
返回值:成功返回0, 失败返回-1
bind接口为套接字绑定的地址:
使用ifconfig指令绑定地址
推荐绑定10000以上
3.发送数据
ssize_t sendto(int sockfd, void* buf, size_t dlen, int flag, struct sockaddr* peer, socklen_t alen);
peer设置的谁,就发给谁
4.接收数据
ssize_t recvfrom(int sockfd, void* buf, size_t dlen, int flag, struct sockaddr* peer, socklen_t *alen)
凡是涉及到获取地址信息的操作,地址信息长度基本都是 socklen_t*
peer用于获取地址信息,peer中存的是谁,数据就是谁发给我的,peer这个参数在recvfrom这函数中设置的
返回值:成功返回实际接收到的数据长度,失败返回-1
5.关闭套接字,释放资源
int close(int fd);
B.字节序相关接口
下面这个几个接口已经进行了主机字节序的判断,不需要担心自己的主机字节序
代码实现、逐步注释
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(int argc, char *argv[])
{
//下面几行,要求程序运行需要输入两个参数,一个为IP地址,一个为端口,加上程序命令本身,共三个参数,argc就表示参数,不等于3就给你提示,需要输入的东西
if(argc != 3)
{
printf("./udp_srv 192.168.2.2 9000\n");
return -1;
}
uint16_t port = atoi(argv[2]);//atoi为字符串转数字,就是9000转赋值port
char *ip =arg[1];//就是地址192.168.2.2
//1.创建套接字
//AF_INET为IPV4地址域类型,SOCK_DGRAM数据报套接字类型--提供数据报传输服务,
//IPPROTO_UDP--表示使用UDP协议
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0)
{
perror("socket error");
retrun -1;
}
//2.为套接字绑定地址信息
struct sockaddr_in addr;//定义一个ipv4的地址结构出来
addr.sin_family = AF_INET;//保存地址域类型
addr.sin_port = htons(port);//一定要用htons,不能用htonl,保存网络字节序端口
addr.sin_addr.s_addr = inet_addr(ip);//网络字节序的IP地址
socklen_t len = sizeof(struct sockaddr_in);
bind(sockfd, (struct sockaddr*)&addr, len);
if(ret == -1)
{
perror("bind error");
return -1;
}
//3.循环接收发送数据
while(1)
{
//接收数据
char buf[1024] = {0};
struct sockaddr_in peer;
socklen_t len = sizeof(struct sockaddr_in);
//注意,peer中的地址信息是系统设置的,数据是发的就设置谁的地址
ssize_t ret = recvfrom(sockfd, buf, 1023, 0, (struct sockaddr*)&peer, &len);
//recvfrom不但接收数据,还会获取这个数据是谁发送的,也即是对端地址信息
if(ret < 0)
{
perror("recvfrom error");
return -1;
}
char *peerip = inet_ntoa(peer.sin_addr);//将网络字节序地址转换为字符串
uint16_t port = ntohs(peer.sin_port);//将网络字节序端口,转换为主机字节序端口
printf("client[%s:%d] say:%s\n", peerip, port, buf);
//4,发送数据
char data[1024]= 0;
printf("server say:");
fflush(stdout);
scanf("%s", data);
ret = sendto(sockfa, data, strlen(data), 0, (struct sockaddr*)&peer, len);
if(ret < 0)
{
perror("sendto error");
return -1;
}
//5.关闭套接字
close(sockfd);
return 0;
}
4.实现一个UDP通信客户端
封装一个UdpSocket类,简化用户对socket的操作
代码在xshell ,UDP文件夹中