网络套接字socket
1 跨主机传输需要注意的问题
1.1 字节序问题
大端存储与小端存储
- 大端:低地址处方高字节
- 小端:低地址处方低字节
主机字节序和网络字节序
若两个主机的字节序存储方式不同,直接传输的数据被对方接收后会就会使完全错误的,为了避免这种情况发生,从而引入主机字节序(host)和网络字节序(network)。即网络中的数据传输用同一中网络字节序,各个主机提供主机字节序与网络字节序相互转换的接口:
- hton(s/l) 主机转网络
- ntoh(s/l) 网络转主机
1.2 对齐
比如对于下面的这个结构体:
struct {
int i;
float f;
char ch;
};
他所占的字节内存空间并不是9个字节而是12个字节,这是因为编译器为了方便数据处理而采取了对齐处理,这样的一个结构体在不同的主机上的对齐方式可能不一样,从而导致传输的数据不能被正确的解析,所以应该让结构体不对齐
即在定义结构体的时候通过宏来告诉编译器不进行对齐处理
1.3 类型长度的问题
不同机器或编译器对于常用数据类型的长度的定义是不一样的,解决办法就是使用诸如以下的数据类型:
- int32_t
- uint32_t
- int63_t
- …
2 socket是什么
一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口。
3 socket函数
-
socket()
用domain协议族中的protocol协议来完成type类型的传输
int socket(int domain, int type, int protocol);
- 成功返回一个socket文件描述符,否则返回-1并设置errno
- domain的值
Name Purpose Man page
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device netlink(7)
AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK AppleTalk ddp(7)
AF_PACKET Low level packet interface packet(7)
AF_ALG Interface to kernel crypto API
- type的值
SOCK_STREAM(流式) Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported.
SOCK_DGRAM(报式) Supports datagrams (connectionless, unreliable messages of a fixed maximum length).
SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with
each input system call.
SOCK_RAW Provides raw network protocol access.
SOCK_RDM Provides a reliable datagram layer that does not guarantee ordering.
SOCK_PACKET Obsolete and should not be used in new programs; see packet(7).
4 报式套接字UDP
4.1 基本过程
被动端(先运行)
- 取得socket
- 给socket取得地址
- 收/发消息
- 关闭socket
主动端
- 取得socket
- 给socket取得地址(可省略,系统给默认的端口)
- 发/收消息
- 关闭socket
4.2 相关函数
socket() 创建socket
int socket(int domain, int type, int protocol);
bind() 绑定对应的端口
sockaddr这个结构体并不存在,需要根据我们socket中所采用的协议族来确定使用什么样的结构体,addrlen就是实际 使用的结构体的大小。成功返回0,失败返回-1并设置errno
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
当采用AF_INET(ipv4)协议的时候,所使用的结构体为:
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET 协议族*/
in_port_t sin_port; /* port in network byte order 绑定的端口*/
struct in_addr sin_addr; /* internet address ip地址*/
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order 整型ip地址*/
};
- 补充函数inet_pton()将点分的IPV4地址转换成一个整型,af是协议族,src是点分IPV4地址,dst是存放整型结果的地址
int inet_pton(int af, const char *src, void *dst);
- inet_ntop() 将大整数IP地址转换成点分式
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);
recvfrom()
从sockfd中接受len字节的数据到buf中,flags为特殊要求,src_addr和addrlen是对端(发送端)地址和地址的长度的地 址
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
sendto()
从sockfd中发送buf中len字节数据到网络中,flags为特殊要求,dest_addr和addrlen为接受端的地址和地址长度信息
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
close() 关闭socket
int close(int fd);
setsockopt() 设置socket的options,成功返回0,失败返回-1并设置errno
- 对sockfd某一个层面level中的某一个属性optname进行设置
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
getsockopt() 获取socket的options
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
4.3 报式传输实例
要传输的数据的格式
#ifndef SOCKET_PROTO_H
#define SOCKET_PROTO_H
#define NAMEMAX (512 - 8 - 8)
#define RCVPORT "1999"
struct msg_st {
u_int32_t math;
u_int32_t chinese;
u_int8_t name[1];
} __attribute__((packed));
#endif //SOCKET_PROTO_H
接收方
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include "proto.h"
#define IPSTRSIZE 64
int main() {
int sd;
struct sockaddr_in laddr, raddr;
struct msg_st rbuf;
socklen_t raddr_len;
char ipstr[IPSTRSIZE];
//1 取得socket用ipv4协议中默认支持报式传输的协议
sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
perror("socket()");
exit(1);
}
//2 给socket绑定地址
laddr.sin_family = AF_INET;
laddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr.s_addr);
if (bind(sd, (void *) &laddr, sizeof (laddr)) < 0) {
perror("bind()");
exit(1);
}
//3 接受信息
raddr_len = sizeof (raddr); /* 注意初始化发送端的地址长度!!! */
while (1) {
recvfrom(sd, &rbuf, sizeof (rbuf), 0, (