IP地址
IP协议有两个版本,IPv4和IPv6,若没有特殊说明的,默认都是指IPv4
- IP地址是在IP协议中,⽤来标识网络中不同主机的地址
- 对于IPv4来说, IP地址是⼀个4字节, 32位的整数
- 我们通常也使⽤ “点分⼗进制” 的字符串表⽰IP地址,例如192.168.0.1 ;⽤点分割的每⼀个数字表示⼀个字节,范围是0 ~ 255
端口号
端⼝号(port)是传输层协议的内容
- 端⼝号是⼀个2字节16位的整数
- 端⼝号⽤来标识⼀个进程,告诉操作系统,当前的这个数据要交给哪⼀个进程来处理
- IP地址 + 端⼝号能够标识网络上的某⼀台主机的某⼀个进程
- ⼀个端⼝号只能被⼀个进程占⽤
网络字节序
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存
- 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址
- TCP/IP协议规定,网络数据流应采⽤大端字节序,即低地址高字节
- 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据
- 如果当前发送主机是小端,就需要先将数据转成大端;否则就忽略,直接发送即可
UDP协议特点
- 传输层协议
- ⽆连接
- 不可靠传输
- ⾯向数据报
所需API
//创建socket⽂件描述符(TCP/UDP,客户端+服务器)
int socket(int domain, int type, int protocol);
//绑定端⼝号(TCP/UDP,服务器)
int bind(int socket, const struct sockaddr* address, socklen_t address_len);
//接收消息
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t *addrlen);
//发送消息
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);
UDP服务器
server.c
//////////////////////////////////////////////////////////////
//服务器逻辑:
//1.启动(初始化)
//2.进入一个死循环(事件循环)
// a)从 socket 中读取请求(Request)
// b)根据 Request 的内容计算生成 Response
// c)把 Response 响应写回到socket
//
// 此处为了实现方便,我们实现一个 echo_server(回显服务器)
///////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
// ./server 127.0.0.1 9090
int main(int argc, char* argv[]){
//0.校验命令行参数
if(argc != 3){
printf("Usage ./server [ip] [port]\n");
return 1;
}
//1.服务器的初始化
// a)创建 socket
// AF_INET:ipv4协议
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd < 0){
perror("socket");
return 1;
}
//b)绑定ip地址端口号
struct sockaddr_in addr;
addr.sin_family = AF_INET;
//点分十进制的字符串ip地址转换成数字(网络字节序)
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
int ret = bind(fd, (sockaddr*)&addr, sizeof(addr));
if(ret < 0){
perror("bind");
return 1;
}
//2.进入死循环
while(1){
//a)读取请求
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
char buf[1024] = {0};
ssize_t read_size = recvfrom(fd, buf, sizeof(buf) - 1, 0, (sockaddr*)&peer, &len);
if(read_size < 0){
perror("recvfrom");
continue;
}
buf[read_size] = '\0';
//b)根据请求进行计算(此处由于实现的是echo_server,所以省略这一步)
printf("[client %s:%d] say:%s\n", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port), buf);
//c)把响应结果写回到socket
//缓冲区长度最好不要写成sizeof,由于我们传输数据只传输有效部分就可以了
sendto(fd, buf, strlen(buf), 0, (sockaddr*)&peer, sizeof(peer));
}
close(fd);
return 0;
}
- socket的参数使⽤SOCK_DGRAM表示UDP
- bind之后就可以直接进⾏通信了
- 使⽤sendto和recvfrom来进行数据读写
查看服务器是否启动成功:
client.c
//////////////////////////////////////////////////////
//客户端逻辑:
//1.用户输入数据,从标准输入输入一个字符串
//2.把这个字符串发送给服务器(请求Request)
//3.从服务器读取返回结果(响应Response)
//4.把返回结果打印到标准输出上
//////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
//./client [server_ip][server_port]
int main(int argc, char* argv[]){
if(argc != 3){
printf("Usage ./client [ip] [port]\n");
return 1;
}
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd < 0){
perror("socket");
return 1;
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
//点分十进制的字符串ip地址转换成数字(网络字节序)
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
server_addr.sin_port = htons(atoi(argv[2]));
while(1){
//1.从标准输入读取数据
char buf[1024] = {0};
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if(read_size < 0){
//此处由于是客户端,所以处理方式和服务器不相同
//服务器要保证不能因为单个客户端的异常数据导致服务器直接终止程序
//但是客户端的话就可以根据需要决定是终止还是忽略错误
perror("read");
return 1;
}
if(read_size == 0){
printf("read done!\n");
return 0;
}
//2.发送数据到服务器
sendto(fd, buf, strlen(buf), 0, (sockaddr*)&server_addr, sizeof(server_addr));
//3.尝试从服务器读取响应
char buf_resp[1024] = {0};
//recvform第五个第六个参数表示对端的ip和端口号
//此时由于客户端收到的数据一定是服务器返回的响应数据
//所以此时就可以忽略掉对端的ip和端口号
read_size = recvfrom(fd, buf_resp, sizeof(buf_resp), 0, NULL, NULL);
if(read_size < 0){
perror("recvform");
return 1;
}
buf_resp[read_size] = '\0';
//4.把响应写到标准输出上
printf("server resp:%s\n", buf_resp);
}
close(fd);
return 0;
}
- socket的参数使⽤SOCK_DGRAM表示UDP
- 使⽤sendto和recvfrom来进行数据读写