Linux系统编程——socket套接字网络编程

一、TCP / UDP 通信协议对比

① TCP 面向连接(如打电话要先拨号建立连接);UDP 是无连接的,即发送数据之前不需要建立连接

② TCP 提供可靠的服务,也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP 尽最大努力交付,即不保证可靠交付

③ TCP 面向字节流,实际上是 TCP 把数据看成一连串无结构的字节流;UDP 是面向报文的,UDP 没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如 IP 电话,实时视频会议等)

④ 每一条 TCP 连接只能是点到点的;UDP 支持一对一,一对多,多对一和多对多的交互通信

⑤ TCP 首部开销20字节;UDP 的首部开销小,只有8个字节

⑥ TCP 的逻辑通信信道是全双工的可靠信道,UDP 则是不可靠信道

二、字节序

1、概述

  字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序

  • Little endian小端字节序:将低序字节存储在起始地址(低地址)
  • Big endian大端字节序:将高序字节存储在起始地址(低地址)
  • 网络字节序是big endian的字节序,x86 系列 CPU 都是little-endian的字节序,所以用 x86 的电脑向网络传输数据,先转换字节序

具体示例:在内存中双字0x01020304(DWORD)的存储方式:

内存地址: 低地址 高地址
内存地址: 4000 4001 4002 4003
LE 0000 0100( 04 ) 0000 0011( 03 ) 0000 0010( 02 ) 0000 0001( 01 )
BE 0000 0001( 01 ) 0000 0010( 02 ) 0000 0011( 03 ) 0000 0100( 04 )
2、字节序转换 api :
#include<arpa/inet.h>

uint16_t htons(uint16_t hostshort);		//返回网络字节序的值
uint32_t htonl(uint32_t hostlong);		//返回网络字节序的值
uint16_t ntohs(uint16_t netshort);		//返回主机字节序的值
uint32_t ntohl(uint32_t netlong);		//返回主机字节序的值
// h 代表 host ,n 代表 net,s 代表 short(2个字节),l 代表 long(4个字节)

三、socket 套接字网络编程

1、步骤:

在这里插入图片描述

2、函数原型:

① 创建套接字:

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
返回值: 成功返回套接字描述符,失败返回 -1
domain参数 协议族,指定所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)
AF_INET IPv4互联网协议
AF_INET6 IPv6互联网协议
AF_UNIX Unix
AF_ROUTE 路由套接字
AF_KEY 密钥套接字
AF_UNSPEC 未指定
more…
type参数 套接字类型描述
SOCK_STREAM 流式套接字提供可靠的、面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性
SOCK_DGRAM 数据报套接字定义了一种无连接的服,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠的、无差错的。它使用数据报协议UDP
SOCK_RAW 允许程序使用底层协议,原始套接字允许对底层协议和IPICMP进行直接访问,功能强大但使用较为不方便,主要用于一些协议的开发
more…
protocol参数 套接口协议类型。通常赋 0 值,表示指定选择与type参数对应的默认协议
0 选择与type参数对应的默认协议
IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议
IPPROTO_TIPC TIPC传输协议
more…

② 为套接字添加信息( IP 地址和端口号):

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值: 成功返回 0,失败返回 -1
sockfd参数: 要绑定的套接字描述符
addr参数: 是一个指向包含本机 IP 地址及端口号等信息的 sockaddr 类型的指针,指向要绑定给 sockfd 的协议地址结构,这个地址结构根据地址创建 socket 时的地址协议族的不同而不同

IPv4 对应的是:

struct sockaddr{
    sa_family_t sa_family;    // 协议族
    char sa_data[14];       // IP + 端口
};

一般用 sockaddr_in 结构体同等替换 sockaddr 结构体:

#include <netinet/in.h>    // 或 #include <linux/in.h>

struct sockaddr_in{
    sa_family_t sin_family;      // 协议族(常用 AF_INET)
    in_port_t sin_port;         // 端口号,用户一般用 5000~9000,注意要转换为网络字节序再赋值
    struct in_addr sin_addr;    // IP 地址结构体
    unsigned char sin_zero[8];   // 填充,没有实际意义,只是为跟 sockaddr 结构在内存中对齐,这样两者才能相互转换
};

IP 地址结构体如下:uint32_t s_addr32 位二进制网络字节序的 IP 地址:

struct in_addr{
    uint32_t s_addr;    // 按 32 位二进制网络字节序排列的 IP 地址,本机地址可用 ifconfig 查看,注意使用地址转换函数进行转换
};

地址转换函数:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp);    // 将 cp 指向的形如 192.168.x.xxx 的字符串 IP 地址转换为按网络字节序排列的 IP 地址,存放在 inp 指向的结构体中

in_addr_t inet_addr(const char *cp);              // 将 cp 指向的形如 192.168.x.xxx 的字符串 IP 地址转换为按网络字节序排列的 IP 地址,存放在返回的结构体中

char *inet_ntoa(struct in_addr in);              // 将结构体 in 中的网络字节序 IP 地址转换为形如 192.168.x.xxx 的字符串 IP 地址

in_addr_t inet_network(const char *cp);
struct in_addr inet_makeaddr(int net, int host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);
addrlen参数: addr参数指向的结构体的大小,可用sizeof()计算

③ 监听被绑定的端口,监听网络连接请求:

#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog);
  • 设置能处理的最大连接数,listen()并未开始接受连线,只是设置sockectlisten模式,listen()只用于服务器端,服务器进程不知道要与谁连接,因此,它不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。主要就两个功能:① 将一个未连接的套接字转换为一个被动套接字(监听);② 规定内核为相应套接字排队的最大连接数。
  • 内核为任何一个给定监听套接字维护两个队列:
    ① 未完成连接队列,每个这样的SYN报文段对应其中一项:已由某个客户端发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程。这些套接字处于SYN_REVD状态;
    ② 已完成连接队列,每个已完成TCP三次握手过程的客户端对应其中一项。这些套接字处于ESTABLISHED状态
返回值: 成功返回 0,失败返回 -1
sockfd参数: 套接字描述符,监听该套接字绑定的端口
backlog参数: 指定在请求队列中允许的最大请求数

④ 连接指定计算机的端口,与服务器建立连接:

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 建立与指定socket的连接,连接时阻塞,直到连接成功
返回值: 成功返回 0,失败返回 -1
sockfd参数: socket套接字描述符
addr参数: 包含目标服务器端IP地址和端口号的结构体,sockaddr结构体见bind()函数addr参数
addrlen参数: addr参数指向的结构体的长度,可用sizeof()计算

⑤ 监听到有客户端接入,接受一个连接:

#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • accept()TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠(如果队列中没有连接请求,‌accept()函数会一直等待,‌直到接收到连接请求才返回)
  • 一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字
  • 已连接套接字(表示 TCP 三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭
返回值: 该函数的返回值是一个新的套接字描述符,表示已连接套接字的描述符,而socket()返回值是服务器监听套接字描述符
sockfd参数: socket套接字描述符
addr参数: 用来返回已连接的对端(客户端)的协议地址,不关心时置NULLsockaddr结构体见bind()函数addr参数
addrlen参数: addr参数指向的结构体的长度,可用sizeof()计算,不关心时置NULL

⑥ 数据收发常用 api:

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

/* -------------------------------------------------------------------- */

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// flags 参数为控制选项,一般可设置为 0

UDP 连接常用收发函数:
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
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);

⑦ 关闭 socket 套接字,断开连接:

#include <unistd.h>

int close(int fd);

四、示例

Server:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
//#include<linux/in.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~莘莘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值