UNIX环境编程(c语言)--套接字--基本TCP套接字编程
目录
- 准备知识
- 字节序
- 字节序转换函数
- 字节操纵函数
- 地址转换函数
- 地址结构
- 基本TCP套接字编程
- 概要
- socket函数
- bind函数
- listen函数
- accept函数
- connect函数
- 通信函数 read 和 write
- 关闭通信close 和 shutdown
准备知识
字节序
假如有一个16位的整数,它占了2字节,有两种存储方法
小端字节序:将低序字节存储在起始地址
大端字节序:将高序字节存储在起始地址
这两种方法都有系统在用,所以网络通信需要转化字节序
我们把某个系统上使用的字节序称为 主机字节序
把网络协议使用的字节序称为 网络字节序
网络字节序都是采用大端字节序
而主机字节序却没有标准而言
字节序转换函数
原型
uint16_t htons(uint16_t h16bitval); // 主机转网络,短型
uint32_t htonl(uint32_t h32bitval); // 主机转网络,长型
uint16_t ntohs(uint16_t n16bitval);// 网络转主机,短型
uint32_t ntohl(uint32_t n32bitval);// 网络转主机,长型
h表示主机,n表示网络,s表示short,l 表示long
字节操纵函数
字节操纵函数有两组
一是
void bzero(void *dest, size_t nbytes);
void bcopy(const void *src, void *dest, size_t nbytes);
int bcmp(const void *ptr1, const void *ptr2, size_t nbytes);
其中bzero是将目标字节串中指定大小的字节数置为0,参数dest是指向首地址的指针,参数nbytes是需要设置的字节数。
bcopy函数用来复制内存的
参数src 为源内存块指针,dest 为目标内存块指针,n 为要复制的内存的前 n 个字节长度
bcmp的功能是比较ptr1和ptr2的前n个字节是否相等
如果ptr1=ptr2或n=0则返回零,否则返回非零值。bcmp不检查NULL。
二是
void *memset(void *dest, int c, size_t len);
void *memcpy(void *dest, const void *src,size_t nbytes);
int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);
memset是将目标字节串的指定数目(len)置为值 c
memcpy,和bcopy作用和用法一致,注意参数顺序不一样
memcmp 功能和bcmp一致
地址转换函数
我们在表示ipv4的IP地址时习惯使用点分十进制来表示,用的是字符串的形式
但是在实际使用中需要使用IP地址的二进制形式
所以需要一个函数来进行互相转换
原型
int inet_aton(const char *strptr, struct in_addr *addrptr)
/*将字符串地址转化为二进制地址 成功返回1,否则返回0*/char *inet_ntoa(struct in_addr inaddr);
/*将二进制地址转换为字符串地址, 返回一个点分十进制表示的字符串地址 */
其中的struct in_addr是地址结构内的成员,接下来会讲解
但是以上两个函数,只支持ipv4的地址
接下来两函数,支持ipv4和ipv6
int inet_pton(int family, const char *strptr, void *addrptr)
/*将字符串地址转化为二进制地址 成功返回1,输入无效返回0,错误返回-1*/char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
/*将二进制地址转换为字符串地址, 返回一个点分十进制表示的字符串地址 */
两个函数的参数family 可以是 AF_INET(表示IPv4),AF_INET6 (表示IPV6)
第一个函数的参数strptr是需要转换的字符串的指针,addrptr用于存放结果
第二个函数的addrptr是需要转换的二进制,strptr是存放结果字符串的指针,len是str的长度,太小会报错
地址结构
很多套接字函数需要一个指向套接字地址结构的指针作为参数,每个协议族都有自己的地址结构体
但是最终都会强制转换为通用的socket地址结构来传参给函数
通用套接字 sockaddr 类型定义:
typedef unsigned short int sa_family_t;
struct sockaddr { sa_family_t sa_family; /* 2 bytes address family, AF_xxx */char sa_data[14]; /* 14 bytes of protocol address */
}
ipv4对应的是sockaddr_in类型定义:
注意这里的sin_addr 是一个结构体,
typedef unsigned short sa_family_t;
typedef uint16_t in_port_t;
struct in_addr {uint32_t s_addr;
};
struct sockaddr_in {uint8_t sin_len;sa_family_t sin_family; /* 2 bytes address family, AF_xxx such as AF_INET */in_port_t sin_port; /* 2 bytes port*/struct in_addr sin_addr; /* 4 bytes IPv4 address*//* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[8]; /* 8 bytes unused padding data, always set be zero */
};
ipv6对应的sockaddr_in6类型定义:
typedef unsigned short sa_family_t;
typedef uint16_t in_port_t;
struct in6_addr
{union{uint8_t __u6_addr8[16];uint16_t __u6_addr16[8];uint32_t __u6_addr32[4];} __in6_u;
}
struct sockaddr_in6 {sa_family_t sin6_family; /*2B*/in_port_t sin6_port; /*2B*/uint32_t sin6_flowinfo; /*4B*/struct in6_addr sin6_addr; /*16B*/uint32_t sin6_scope_id; /*4B*/
};
Unix域对应的sockaddr_un类型定义:
#define UNIX_PATH_MAX 108
struct sockaddr_un {sa_family_t sun_family; char sun_path[UNIX_PATH_MAX];
};
在使用地址结构前,我们一般先将所有字节都置0,使用czero或memset函数,然后在赋值
接下来我们将在使用时再讨论
基本TCP套接字编程
概要
基本流程
socket函数
socket函数的作用,就相当于我们要读文件时要先open
socket会创建一个socket描述符(和文件描述符一样),后续将使用它进行连接等操作
对于服务器来说,这个描述符是用于监听连接的,实际连接传输的描述符在accept处介绍
原型
int socket(int family, int type, int prttocol);
/* 成功返回描述符,出错返回-1
注意以下出参数并不是任意的搭配都是有效的
一般SOCK_STREAM是TCP/SCTP
SOCK_DGRAM是UDP
两个都可以和AF_INET、AF_INET6搭配
参数family,表示协议族,取值有
取值 | 含义 |
---|---|
AF_INET | IPV4 |
AF_INET6 | IPV6 |
AF_LOCAL | UNIX域协议 |
AF_ROUTE | 路由套接字 |
AF_KEY | 秘钥套接字 |
参数type指明套接字类型
取值 | 含义 |
---|---|
SOCK_STREAM | 字节流套接字 |
SOCK_DGRAM | 数据套接字 |
SOCK_SEQPACKET | 有序分组套接字 |
SOCK_RAW | 原始套接字 |
参数protocol是某个协议类型常值,通常设为0,让其默认选择。
应用实例
进行tcp连接时
socket(AF_INET,SOCK_STREAM,0);
bind函数
将一个本地协议地址赋予一个套接字,就是绑定ip地址和端口的
通常服务器在启动的时候都会绑定一个众所周知的地址(如
ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,由系统自动分配一个端口号和自身的ip地址组合。
通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
当然客户端也可以在调用connect()之前bind一个地址和端口,这样就能使用特定的IP和端口来连服务器了
原型
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/* 成功返回0,失败返回-1 */
参数sockfd:是socket描述字,bind()函数就是将给这个描述字绑定地址和端口
参数addrlen:对应的是地址的长度
参数addr:地址结构指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,但最终都会强制转换后赋值给sockaddr这种类型的指针传给内核
应用实例
struct socket_in sockaddr_in;
int port = 12345;memset(&sockaddr_in,0,sizeof(sockaddr_in));sockaddr_in.sin_family = AF_INET;
sockaddr_in.sin_port = htons(port);
sockaddr_in.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY 代表可以运行任何ip连接bind(socket_fd, (struct sockaddr *)&sockaddr_in,sizeof(sockaddr_in));
listen函数
socket创建的描述符,默认是一个主动类型的(就是主动调用connect去连接别人的,是一个客户端),调用listen后转为主动的,并开始监听socket描述符,等待用户连接
原型
int listen(int sockfd, int backlog);
/* 成功返回0,失败返回-1 */
参数sockfd,是socket描述符
参数backlog 是最大连接个数
最大连接数说明
TCP建立连接是要进行三次握手,但是完成三次握手后,服务器需要维护这种状态:
半连接状态为:服务器处于Listen状态时收到客户端SYN报文时放入半连接队列中,即SYN queue(服务器端口状态为:SYN_RCVD)。
全连接状态为:TCP的连接状态从服务器(SYN+ACK)响应客户端后,到客户端的ACK报文到达服务器之前,则一直保留在半连接状态中;
在Linux内核2.2之前,backlog大小包括半连接状态和全连接状态两种队列大小,
在Linux内核2.2之后,分离为两个backlog来分别限制半连接(SYN_RCVD状态)队列大小和全连接(ESTABLISHED状态)队列大小
SYN queue 队列长度由 /proc/sys/net/ipv4/tcp_max_syn_backlog 指定,默认为2048。
Accept queue 队列长度由 /proc/sys/net/core/somaxconn 和使用listen函数时传入的参数,二者取最小值。默认为
128
accept函数
服务器在调用accept函数后,会阻塞监听socket,等待客户端连接,并返回一个全新的描述符fd,代表与客户端的tcp连接
原型
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/* 成功返回一个用于连接的描述符,失败返回-1 */
参数sockfd: 服务器开始调用socket()函数生成的,称为监听socket描述字;
参数*addr: 用于返回客户端的协议地址,这个地址里包含有客户端的IP和端口信息等,结构体与bind中的一致
参数addrlen: 返回客户端协议地址的长度
accept函数的返回值是由内核自动生成的一个全新的描述字(fd),代表与返回客户的TCP连接。如果想发送数据给该客户端,则我们可以调用write()等函数往该fd里写内容即可;而如果想从该客户端读内容则调用read()等函数从该fd里读数据即可。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个新的socket描述字,当服务器完成了对某个客户的服务,就应当把该客户端相应的的socket描述字关闭。
connect函数
tcp客户端在创建socket后,使用connect来连接服务器
这两个文件描述符(客户端connect的fd和服务器端accept返回的fd)就可以实现客户端和服务器端的相互通信。
原型
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/* 成功返回0,失败返回-1 */
sockfd: 客户端的socket()创建的描述字
addr: 要连接的服务器的socket地址结构,这里面包含有服务器的IP地址和端口等信息,和bind的一致
addrlen: socket地址的长度
实例
struct socket_in sockaddr_in;
int port = 12345;
char *ip = “192.168.1.1”
memset(&sockaddr_in,0,sizeof(sockaddr_in));sockaddr_in.sin_family = AF_INET;
sockaddr_in.sin_port = htons(port);
inet_aton(ip, &sockaddr_in,sin_addr);connect(socket_fd, (struct sockaddr *)sockaddr_in, sizeof(sockaddr_in));
通信函数 read 和 write
以上的函数已足够服务器和客户端建立tcp连接
接下来我们介绍用于通信的函数
ssize_t read(int fd, void *buf, size_t count);ssize_t write(int fd, const void *buf, size_t count);ssize_t send(int sockfd, const void *buf, size_t len, int flags);ssize_t recv(int sockfd, void *buf, size_t len, int flags);ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr,
socklen_t addrlen);ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t
*addrlen);ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
其中read 和 write用法与文件io中的一致,用法如下
UNIX环境编程(c语言)–文件I/O-文件共享
其他函数也不再一一介绍,用法大同小异,详细用法可以man手册查看
关闭通信close 和 shutdown
使用close关闭通信,就和文件io中的用法一样,详情看上面那个链接文件io的文章
如果对socket fd调用close()则会触发该TCP连接断开的四路握手,有些时候我们需要数据发送出去并到达对方之后才能关闭
socket套接字,则可以调用shutdown()函数来半关闭套接字:
int shutdown(int sockfd, int how);
如果how的值为 SHUT_RD 则该套接字不可再读入数据了; 如果how的值为 SHUT_WR 则该套接字不可再发送数据了; 如
果how的值为 SHUT_RDWR 则该套接字既不可以读,也不可以写数据了
UNIX环境编程(c语言)--套接字--基本TCP套接字编程相关推荐
- c语言从1加到任意数的编程,c语言:从键盘任意输入一个整数n,编程计算并输出1-n之间的所有素数之和...
#include #include int prime(int x){ int i; for(i=2;i<=(int)sqrt(x);i++) if(x%i==0) return 0; retu ...
- linux网络编程(6)基于多进程的TCP服务器与客户端编程
服务器端: #include <netdb.h> #include <sys/socket.h> #include <time.h> #include <un ...
- TCP/IP网络编程---Linux系统下的TCP套接字编程
目录 第一章 理解网络编程和套接字 1.1 网络编程和套接字概要 1.2 基于Linux的文件操作 1.2.1 底层文件访问和文件描述符 1.2.2 打开文件 1.2.3 关闭文件 1.2.4 将数据 ...
- TCP套接字编程详解
目录 为什么socket编程又叫套接字编程? TCP服务端 初始化套接字库--WSAStarup 创建套接字--socket 绑定到本机 --bind 开始监听 --listen 连接客户端请求--a ...
- Experiment 0x1:TCP套接字编程
Experiment 0x1:TCP套接字编程 文章目录 Experiment 0x1:TCP套接字编程 0x0 说明 0x1 要求 0x2 实现 0x3 源码 1- TCP服务端源码 2- TCP客 ...
- C语言socket()函数解析(创建套接字)af地址族,ip地址类型(Address Family)INET(Inetnet)PF(Protocol Family)
文章目录 Linux 下的 socket() 函数 1) af 为地址族(Address Family),也就是 IP 地址类型 2) type 为数据传输方式/套接字类型 3) protocol 表 ...
- 新概念c语言教程答案,新概念编程C语言篇习题解答
摘要: <21世纪普通高校计算机公共课程规划教材:新概念编程C语言篇习题解答>对<新概念编程C语言篇>教材中的习题进行了系统全面的分析和解答.习题精选了C语言编程中典型题型,计 ...
- 《UNIX环境编程》第十六章--网络IPC:套接字
Contents 套接字接口 套接字描述符 寻址 字节序 地址格式 地址查询 绑定地址 建立连接 数据传输 套接字选项 带外数据 UNIX域套接字 使用套接字的示例 面向连接的ruptime 无连接的 ...
- UNIX环境高级编程 学习笔记 第十六章 网络IPC:套接字
socket的设计目标之一:同样的接口既可以用于计算机间通信,也可以用于计算机内通信.socket接口可采用许多不同的网络协议进行通信,本章讨论限制在因特网事实上的通信标准:TCP/IP协议栈. 套接 ...
最新文章
- java 定义多个变量_学了Java才搞懂JMeter测试计划
- ASP.NET - JQuery的.getJSON给Dropdownlist绑定Item
- qt 提升 全局包含_研讨引思想碰撞,学习促共同提升 | 粤渝两地幼教同行深度交流...
- Web安全实践(12)密码探测
- 《编码的奥秘》读后感
- vs2019中git提交代码的步骤
- UVa1543.圆和多边形+UVa12063.零和一(动态规划)
- 百度搜索下拉框,下拉菜单怎么做?如何刷?
- 转载:开源license总结
- 高并发服务端分布式系统设计概要
- Java日志框架:Logback
- 一篇文让你秒懂CDN
- OSGi架构学习与设计
- springboot项目在线程中调用service访问数据库
- 高学历者贫穷意味着耻辱和失败?[转]
- nginx 资源动静分离
- Overall simulation report
- 《中学历史核心素养校本化实施的培育研究》阶段性研究会议召开
- 九章算法面试题2 抄书问题
- Python黑魔法手册,共计 128 个黑魔法实例 一次学到嗨