目录
一,网络字节序
二,socket编程接口
sockaddr结构
源IP地址、目的IP地址,在IP数据包头部,有此两个IP地址;
端口号,是传输层协议的内容;
- 端口号是一个2字节16位的整数;
- 端口号用来标识一个进程,告诉操作系统当前数据要交给哪个进程来处理;
- IP地址+端口号能标识网络上的某台主机的某个进程;
- 一个端口号只能被一个进程占用;
一个进程可以绑定多个端口号,但一个端口号不能被多个进程绑定;
源端口号、目的端口号,传输层协议(TCP、UDP)的数据段中有此两个端口号,描述“数据是谁发的,要发给谁”;
TCP协议(Transmission Control Protocol 传输控制协议)
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
UDP协议(User Datagram Protocol 用户数据报协议)
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
一,网络字节序
内存中的多字节数据相当于内存地址有大小端之分,磁盘文件中的多字节数据相当于文件中的偏移也有大小端之分,网络数据流同样也有大小端之分;
如何定义网络数据流的地址:
- 发送主机通常将发送缓冲区中的数据按内存从低到高的顺序发送;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
- 因此网络数据流的地址,先发出的数据是低地址,后发出的数据是高地址;
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节;
- 不管这台主机是大端机还是小段机,都会按照TCP/IP协议规定的网络字节序来发送、接收数据;
- 如当前发送主机是小段,需先将数据转成大端,否则忽略直接发送即可;
为使网络程序具有可移植性,同样C代码在大端机和小端机上编译后都能正常运行,可调用以下库函数做网络字节序和主机字节序的转换;
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
- h,表示host,n表示network,l表示32位长整数,s表示16位短整数;
- 如htonl表示32位长整数从主机字节序转换为网络字节序;
- 如主机是小端字节序,这些函数将参数做相应的大小端转换,然后返回;
- 如主机是大端字节序,这些函数不做转换,然后返回;
二,socket编程接口
socket常见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); //开始监听socket(TCP,服务器) int listen(int socket, int backlog); //接收请求(TCP,服务器) int accept(int socket, struct sockaddr* address, socklen_t address_len); //建立连接(TCP,客户端) int connect(int sockid, const struct sockaddr* addr, socklen_t addlen);
sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,及UNIX Domain Socket;然而各种网络协议的地址格式并不相同;
- IPv4、IPv6地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位IP地址;
- IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET16;这样,只要取得某种sockadddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容;
- socket API可以都用struct sockaddr*类型表示,在使用的时候需要强制转化sockaddr_in;这样的好处是程序的通用性,可以接收IPv4、IPv6,及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;
//socketaddr 结构
struct sockaddr{
__SOCKADDR_COMMON (sa_);
char sa_data[14];
};
//sockadddr_in 结构
struct sockaddr_in{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port;
struct in_addr_ sin_addr;
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
虽然socket API的接口是sockaddr,但是真正在基于IPv4编程时,使用的数据结构是sockaddr_in;这个结构里主要有三部分信息:地址类型、端口号、IP地址;
//in_addr 结构
typedef uint32_t in_addr_t;
struct in_addr{
in_addr_t s_addr;
};
in_addr用来表示一个IPv4的IP地址,其实就是一个32位的整数;
//UDP server.cc
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8081
int main()
{
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if(sock < 0){
std::cerr << "socket error" << std::endl;
return 1;
}
std::cout << "sock: " << sock << std::endl;
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(PORT); //
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){
std::cout << "bind error " << std::endl;
return 1;
}
char message[1024];
for(; ;){
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t s = recvfrom(sock, message, sizeof(message)-1, 0, (struct sockaddr*)&peer, &len);
if(s > 0){
message[s] = '\0';
std::cout << "client# " << message << std::endl;
sendto(sock, message, strlen(message), 0, (struct sockaddr*)&peer, len);
}
else{
//todo
}
}
close(sock);
return 0;
}