网络套接字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, (void *) &raddr, &raddr_len);
inet_ntop(AF_INET, &raddr.sin_addr.s_addr, ipstr, IPSTRSIZE);
printf("---MSSAGE FROM %s:%d---\n", ipstr, ntohs(raddr.sin_port));
printf("name = %s, math = %d, chinese = %d\n", rbuf.name, ntohl(rbuf.math), ntohl(rbuf.chinese));
}
//4 关闭socket
close(sd);
exit(0);
}
发送方
int main(int argc, char *argv[]) {
int sd, size;
struct msg_st *sbufp;
struct sockaddr_in raddr;
if (argc < 3) {
fprintf(stderr, "Usage....\n");
exit(1);
}
if (strlen(argv[2]) > NAMEMAX) {
fprintf(stderr, "Name is too long...\n");
exit(1);
}
//取得socket
sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
perror("socket()");
exit(1);
}
//bind()
//发送消息
size = sizeof (struct msg_st) + strlen(argv[2]);
sbufp = malloc(size);
if (sbufp == NULL) {
perror("malloc()");
exit(1);
}
strcpy(sbufp->name, argv[2]);
sbufp->chinese = htonl(rand() % 100);
sbufp->math = htonl(rand() % 100);
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET, argv[1], &raddr.sin_addr.s_addr);
if (sendto(sd, sbufp, size, 0, (void *) &raddr, sizeof (raddr)) < 0) {
perror("sendto()");
exit(1);
}
//关闭socket
close(sd);
exit(0);
}
4.4 广播实例
广播需要对发送方的实现进行修改,设置options并且将广播的地址设置为"255.255.255.255"
int main(int argc, char *argv[]) {
int sd, size, val = 1;
struct msg_st *sbufp;
struct sockaddr_in raddr;
if (argc < 2) {
fprintf(stderr, "Usage....\n");
exit(1);
}
if (strlen(argv[1]) > NAMEMAX) {
fprintf(stderr, "Name is too long...\n");
exit(1);
}
//取得socket
sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
perror("socket()");
exit(1);
}
if (setsockopt(sd, SOL_SOCKET, SO_BROADCAST, &val, sizeof (val)) < 0) {
perror("setsockopt()");
exit(1);
}
//bind()
//发送消息
size = sizeof (struct msg_st) + strlen(argv[1]);
sbufp = malloc(size);
if (sbufp == NULL) {
perror("malloc()");
exit(1);
}
strcpy(sbufp->name, argv[1]);
sbufp->chinese = htonl(rand() % 100);
sbufp->math = htonl(rand() % 100);
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET, "255.255.255.255", &raddr.sin_addr.s_addr);
if (sendto(sd, sbufp, size, 0, (void *) &raddr, sizeof (raddr)) < 0) {
perror("sendto()");
exit(1);
}
free(sbufp);
//关闭socket
close(sd);
exit(0);
}
4.5 多播/组播实例
proto头文件中添加内容
#define MTGROUP "224.2.2.2"
rcver接受端 加入多播组
int main() {
int sd, size;
struct sockaddr_in laddr, raddr;
struct msg_st *rbufp;
socklen_t raddr_len;
char ipstr[IPSTRSIZE];
size = sizeof (struct msg_st) + NAMEMAX - 1;
rbufp = malloc(size);
if (rbufp == NULL) {
perror("malloc()");
exit(1);
}
//1 取得socket用ipv4协议中默认支持报式传输的协议
sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
perror("socket()");
exit(1);
}
//加入多播组
struct ip_mreqn mreq;
inet_pton(AF_INET, MTGROUP, &mreq.imr_multiaddr);
inet_pton(AF_INET, "0.0.0.0", &mreq.imr_address);
mreq.imr_ifindex = if_nametoindex("eth0");
if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
perror("setsockopt()");
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, rbufp, size, 0, (void *) &raddr, &raddr_len);
inet_ntop(AF_INET, &raddr.sin_addr.s_addr, ipstr, IPSTRSIZE);
printf("---MSSAGE FROM %s:%d---\n", ipstr, ntohs(raddr.sin_port));
printf("name = %s, math = %d, chinese = %d\n", rbufp->name, ntohl(rbufp->math), ntohl(rbufp->chinese));
}
//4 关闭socket
close(sd);
exit(0);
}
发送端snder 创建多播组
int main(int argc, char *argv[]) {
int sd, size;
struct msg_st *sbufp;
struct sockaddr_in raddr;
if (argc < 2) {
fprintf(stderr, "Usage....\n");
exit(1);
}
if (strlen(argv[1]) > NAMEMAX) {
fprintf(stderr, "Name is too long...\n");
exit(1);
}
//取得socket
sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
perror("socket()");
exit(1);
}
//创建多播组
struct ip_mreqn mreq;
inet_pton(AF_INET, MTGROUP, &mreq.imr_multiaddr);
inet_pton(AF_INET, "0.0.0.0", &mreq.imr_address);
mreq.imr_ifindex = if_nametoindex("eth0");
if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, &mreq, sizeof (mreq)) < 0) {
perror("setsockopt()");
exit(1);
}
//bind()
//发送消息
size = sizeof (struct msg_st) + strlen(argv[1]);
sbufp = malloc(size);
if (sbufp == NULL) {
perror("malloc()");
exit(1);
}
strcpy(sbufp->name, argv[1]);
sbufp->chinese = htonl(rand() % 100);
sbufp->math = htonl(rand() % 100);
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET, MTGROUP, &raddr.sin_addr.s_addr);
if (sendto(sd, sbufp, size, 0, (void *) &raddr, sizeof (raddr)) < 0) {
perror("sendto()");
exit(1);
}
free(sbufp);
//关闭socket
close(sd);
exit(0);
}
5 流式套接字TCP
5.1 基本过程
客户端C端
- 获取socket
- 给socket取得地址
- 发送连接请求
- 收发消息
- 关闭连接
服务端S端
- 获取socket
- 给socket取得地址
- 将socket置为监听模式
- 接受连接
- 收发消息
- 关闭连接
5.2 相关函数
listen() 监听一个socket
backlog原为半连接队列的大小,现将半连接队列取消后,该值代表全连接队列的大小。成功返回0,失败返回-1并设 置errno
int listen(int sockfd, int backlog);
accept() 在socket基础上建立一个连接
addr为对端的地址信息
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
send() 发送消息
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
connect() 建立连接
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
5.3 流式传输实例
proto协议头文件
#ifndef SOCKET_PROTO_H
#define SOCKET_PROTO_H
#define SERVERPORT "1999"
#define FMT_STAMP "%lld\r\n"
#endif //SOCKET_PROTO_H
client
int main(int argc, char *argv[]) {
int sd;
struct sockaddr_in raddr;
FILE *fp;
long long stamp;
if (argc < 2) {
fprintf(stderr, "Usage...\n");
exit(1);
}
//1 取得socket
sd = socket(AF_INET,SOCK_STREAM, 0);
if (sd < 0) {
perror("socket()");
exit(1);
}
//2 绑定端口bind()
//3 发起连接
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(SERVERPORT));
inet_pton(AF_INET, argv[1], &raddr.sin_addr);
if (connect(sd, (void *) &raddr, sizeof (raddr)) < 0) {
perror("connect()");
exit(1);
}
//4 接受数据
fp = fdopen(sd, "r+");
if (fp == NULL) {
perror("fdopen()");
exit(1);
}
if (fscanf(fp, FMT_STAMP, &stamp) < 1) {
fprintf(stderr, "Bad format!\n");
exit(1);
} else {
fprintf(stdout, "stamp = %lld\n", stamp);
}
fclose(fp);
exit(0);
}
server
#define IPSTRSIZE 40
#define BUFSIZE 1024
static void server_job(int sd) {
int len;
char buf[BUFSIZE];
len = sprintf(buf, FMT_STAMP, (long long)time(NULL));
if (send(sd, buf, len, 0) < 0) {
perror("send()");
exit(1);
}
}
int main(int argc, char *argv[]) {
int sd, newsd;
struct sockaddr_in laddr, raddr;
socklen_t rsize;
char ipstr[IPSTRSIZE];
pid_t pid;
//1 取得socket
sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd < 0) {
perror("socket()");
exit(1);
}
int val = 1;
if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof (val)) < 0) {
perror("setsickopt()");
exit(1);
}
//2 绑定要监听的端口
laddr.sin_family = AF_INET;
laddr.sin_port = htons(atoi(SERVERPORT));
inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr);
if (bind(sd, (void *) &laddr, sizeof (laddr)) < 0) {
perror("bind()");
exit(1);
}
//3 开始监听
if (listen(sd, 200) < 0) {
perror("listen()");
exit(1);
}
while (1) {
//4 接受连接
rsize = sizeof(raddr);
newsd = accept(sd, (void *) &raddr, &rsize);
if (newsd < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
perror("accept()");
exit(1);
}
pid = fork();
if (pid < 0) {
perror("fork()");
exit(1);
}
if (pid == 0) {
close(sd);
inet_ntop(AF_INET, &raddr.sin_addr, ipstr, IPSTRSIZE);
printf("Client:%s:%d\n", ipstr, ntohs(raddr.sin_port));
//5 发送数据
server_job(newsd);
close(newsd);
exit(0);
}
close(newsd);
}
//6 关闭连接
close(sd);
exit(0);
}
需要注意的是,上面的程序中父子进程都要关闭newsd,这样服务端和客户端的连接就全部关闭了,这时客户端的连 接也会自动关闭,此时会刷新fscanf的缓冲区才能读取到数据。若父子进程有一个newsd没有关闭,则服务端和客户 端的连接一直存在从而不能刷新fcanf的缓冲区导致客户端不能显示时间戳数据
服务端代码用静态进程池来实现
#define IPSTRSIZE 40
#define BUFSIZE 1024
#define PROCNUM 4
static void server_job(int sd) {
int len;
char buf[BUFSIZE];
len = sprintf(buf, FMT_STAMP, (long long)time(NULL));
if (send(sd, buf, len, 0) < 0) {
perror("send()");
exit(1);
}
}
static void server_loop(int sd) {
int newsd, rsize;
struct sockaddr_in raddr;
char ipstr[IPSTRSIZE];
// 接受连接
rsize = sizeof(raddr);
while (1) {
newsd = accept(sd, (void *) &raddr, &rsize);
if (newsd < 0) {
if (errno == EAGAIN || errno == EINTR) return;
perror("accept()");
exit(1);
}
inet_ntop(AF_INET, &raddr.sin_addr, ipstr, IPSTRSIZE);
printf("Client:%s:%d\n", ipstr, ntohs(raddr.sin_port));
//5 发送数据
server_job(newsd);
close(newsd);
exit(0);
}
}
int main(int argc, char *argv[]) {
int sd, i;
struct sockaddr_in laddr, raddr;
socklen_t rsize;
pid_t pid;
//1 取得socket
sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd < 0) {
perror("socket()");
exit(1);
}
int val = 1;
if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof (val)) < 0) {
perror("setsickopt()");
exit(1);
}
//2 绑定要监听的端口
laddr.sin_family = AF_INET;
laddr.sin_port = htons(atoi(SERVERPORT));
inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr);
if (bind(sd, (void *) &laddr, sizeof (laddr)) < 0) {
perror("bind()");
exit(1);
}
//3 开始监听
if (listen(sd, 200) < 0) {
perror("listen()");
exit(1);
}
for (i = 0; i < PROCNUM; i++) {
pid = fork();
if (pid < 0) {
perror("fork()");
exit(1);
}
if (pid == 0) {
server_loop(sd);
exit(0);
}
}
for (i = 0; i < PROCNUM; i++) {
wait(NULL);
}
close(sd);
exit(0);
}