1.IP
- ip是网络层的协议
- IP数据报中封装了源ip地址和目的ip地址,从而知道数据是谁的,又要发给谁.
2.端口号
- 端口号(port)是传输层协议的内容.
- 端口号是一个2字节16位的整数;
- 端口号用来标识一个进程, 告诉操作系统,当前的这个数据要交给哪一个进程来处理; IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
- 一个端口号只能被一个进程占用
- 一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定
3.UDP协议
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
4.网络字节序
内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏 移地址也有大端小端之分,网络数据流同样有大端小端之分
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
- 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
- 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
- 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
c语言库函数 网络字节序和主机字节序的转换
#include<arpa/inet.h>
uint32_t htonl(uint32_t hostlong);//,h表示host,n表示network,
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
5.基于socket API编写一个UDP回显服务器
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
//UDP版本的服务器
//服务器的基本工作流程
//1.初始化(服务器启动)
//2.进入一个主循环
// a.读取客户端发来的"请求"(Request)
// b.根据请求内容,计算生成"响应(Response)内容最核心
// c.把响应数据返回客户端
int main()
{
//1.先创建一个socket
//AF_INET 是一个宏,表示使用ipv4协议
//SOCK_DGRAM表示UDP协议
int sock = socket(AF_INET,SOCK_DGRAM, 0);
if(sock < 0)
{
perror("sock");
return -1;
}
//2.把当前的socket绑定到一个ip + 端口号
sockaddr_in addr;//是一个结构体 存放 ip+端口号
addr.sin_family = AF_INET;//ipv4协议
addr.sin_addr.s_addr = inet_addr("0.0.0.0");//ip地址,函数自己转换了网络字节序
addr.sin_port = htons(9090);//端口号必须得先转换成网络字节序
int ret = bind(sock,(sockaddr*)&addr,sizeof(addr));//绑定到socket
if(ret < 0){
perror("bind");
return -1;
}
printf("server start ok!\n");
//3.处理服务器收到的请求
while(1)
{
//a)读取客户端的请求
//面向数据报的函数接口
sockaddr_in peer;
socklen_t len = sizeof(peer);
char buf[1024] = {0};
//接受客户端消息 peer来获取客户端的ip以及端口号,buf存消息
ssize_t n = recvfrom(sock,buf,sizeof(buf)-1,0,(sockaddr*)&peer,&len);
if(n < 0){
perror("recvform");
continue;
}
buf[n] = '\0';
//显示客户端发来的消息
printf("[%s:%d] buf: %s\n", inet_ntoa(peer.sin_addr),
ntohs(peer.sin_port),
buf);
//b)根据请求计算响应
//TODO 业务逻辑
//c)把响应写回客户端
n = sendto(sock,buf,strlen(buf),0,(sockaddr*)&peer,len);
if(n < 0){
perror("sendto");
continue;
}
//netstat -anp |grep 9090
}
close(sock);//关闭
return 0;
}
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#include<stdio.h>
#include<sys/socket.h>
int main(int argc, char* argv[])
{
//1.先去创建一个socket
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0){
perror("socket");
return -1;
}
//客户端一般不需要bind
//如果没有bind,操作系统会随机分配一个端口号
//如果是服务器程序不去bind就会导致每次启动服务端口号不一样
//客户端不知道服务器端口而导致无法连接
//客户端bind的话会出问题
//一个端口号只能bind一个进程
//2.准备好服务器的sockeaddr_in结构
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argv[1]);//参数输入ip
server_addr.sin_port = htons(9090);
//3.客户端直接发送数据即可
while(1)
{
printf("请输入一段内容\n");
char buf[1024] = {0};
scanf("%s",buf);
//向客户端发送数据
ssize_t n = sendto(sock,buf,strlen(buf),0,(sockaddr*)&server_addr,sizeof(server_addr));
if(n < 0){
perror("sendto\n");
continue;
}
//接收服务器发回来的消息
char buf_output[1024] = {0};
recvfrom(sock,buf_output,sizeof(buf_output)-1,0,NULL,NULL);
printf("server: %s\n",buf_output);
}
close(sock);
return 0;
}