目录

  • 准备知识
    • 字节序
      • 字节序转换函数
    • 字节操纵函数
    • 地址转换函数
    • 地址结构
  • 基本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套接字编程相关推荐

  1. 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 ...

  2. linux网络编程(6)基于多进程的TCP服务器与客户端编程

    服务器端: #include <netdb.h> #include <sys/socket.h> #include <time.h> #include <un ...

  3. TCP/IP网络编程---Linux系统下的TCP套接字编程

    目录 第一章 理解网络编程和套接字 1.1 网络编程和套接字概要 1.2 基于Linux的文件操作 1.2.1 底层文件访问和文件描述符 1.2.2 打开文件 1.2.3 关闭文件 1.2.4 将数据 ...

  4. TCP套接字编程详解

    目录 为什么socket编程又叫套接字编程? TCP服务端 初始化套接字库--WSAStarup 创建套接字--socket 绑定到本机 --bind 开始监听 --listen 连接客户端请求--a ...

  5. Experiment 0x1:TCP套接字编程

    Experiment 0x1:TCP套接字编程 文章目录 Experiment 0x1:TCP套接字编程 0x0 说明 0x1 要求 0x2 实现 0x3 源码 1- TCP服务端源码 2- TCP客 ...

  6. C语言socket()函数解析(创建套接字)af地址族,ip地址类型(Address Family)INET(Inetnet)PF(Protocol Family)

    文章目录 Linux 下的 socket() 函数 1) af 为地址族(Address Family),也就是 IP 地址类型 2) type 为数据传输方式/套接字类型 3) protocol 表 ...

  7. 新概念c语言教程答案,新概念编程C语言篇习题解答

    摘要: <21世纪普通高校计算机公共课程规划教材:新概念编程C语言篇习题解答>对<新概念编程C语言篇>教材中的习题进行了系统全面的分析和解答.习题精选了C语言编程中典型题型,计 ...

  8. 《UNIX环境编程》第十六章--网络IPC:套接字

    Contents 套接字接口 套接字描述符 寻址 字节序 地址格式 地址查询 绑定地址 建立连接 数据传输 套接字选项 带外数据 UNIX域套接字 使用套接字的示例 面向连接的ruptime 无连接的 ...

  9. UNIX环境高级编程 学习笔记 第十六章 网络IPC:套接字

    socket的设计目标之一:同样的接口既可以用于计算机间通信,也可以用于计算机内通信.socket接口可采用许多不同的网络协议进行通信,本章讨论限制在因特网事实上的通信标准:TCP/IP协议栈. 套接 ...

最新文章

  1. java 定义多个变量_学了Java才搞懂JMeter测试计划
  2. ASP.NET - JQuery的.getJSON给Dropdownlist绑定Item
  3. qt 提升 全局包含_研讨引思想碰撞,学习促共同提升 | 粤渝两地幼教同行深度交流...
  4. Web安全实践(12)密码探测
  5. 《编码的奥秘》读后感
  6. vs2019中git提交代码的步骤
  7. UVa1543.圆和多边形+UVa12063.零和一(动态规划)
  8. 百度搜索下拉框,下拉菜单怎么做?如何刷?
  9. 转载:开源license总结
  10. 高并发服务端分布式系统设计概要
  11. Java日志框架:Logback
  12. 一篇文让你秒懂CDN
  13. OSGi架构学习与设计
  14. springboot项目在线程中调用service访问数据库
  15. 高学历者贫穷意味着耻辱和失败?[转]
  16. nginx 资源动静分离
  17. Overall simulation report
  18. 《中学历史核心素养校本化实施的培育研究》阶段性研究会议召开
  19. 九章算法面试题2 抄书问题
  20. Python黑魔法手册,共计 128 个黑魔法实例 一次学到嗨

热门文章

  1. Linux笔记2_Linux图形界面简介
  2. Cisco-SRWE-交换的概念、VLAN和VLAN间路由考试测试题
  3. 数据校验码(奇偶校验,海明校验,循环冗余校验)内容总结及个人经验分享
  4. 旅游负面舆情暴发,中移能力开放商店舆情监测系统助你应对有方!
  5. typescript提示类型可能为null
  6. Caffe练习(二):图片性别识别
  7. 计算机中级职称考试模拟,哪位大侠有中级职称计算机考试的模拟题啊
  8. Simulink中根据变压器铭牌参数设定参数
  9. 无缝轮播——改ul的marginLeft
  10. 常见的12个深度学习面试问题