linux c socket编程学习(1)(一些基本概念及基本的函数使用)

本文详细介绍了TCP/IP协议的基本概念,包括IP地址、TCP与UDP的特点、数据帧格式和传输机制。同时,深入探讨了套接字在编程中的应用,包括socket函数、bind、connect、listen、accept、write和read等关键函数的使用。此外,还讲解了TCP的三次握手和四次挥手过程,以及UDP的不可靠传输特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、相关理论:

部分理论资料来自TCP/IP sockets编程(C语言实现),额外做了许多补充。

概念:

IP
每台主机的IP地址都是不同的。主机的IP地址公网IP内网IP。在局域网中,每台主机的IP都取自192.168.1.1-192.168.1.255C类)之间,不存在两台主机的IP重复,此IP是局域网的内网IP,由用户自行分配。公网IP是访问互联网的IP,每次联机到互联网时会随机分配,互联网上的每一台主机的公网IP都不重复。内网IP通过交换机或路由器搭配主机构建,而公网IP是由运营商运营,作用在ADSL设备上。查看内网IP时,通过主机终端执行命令ifconfig(Linux)\ ipconfig(Windows)查看,上面显示的IP就是主机在当前路由的局域网中的IP。而公网IP则可以通过百度搜索IP地址获取,如我当前使用的主机的公网IP内网IP
在这里插入图片描述
协议
是关于由通信程序交换的分组及其含义的协定。TCP/IP协议族中的主要协议是IP(网络协议)TCP(传输控制协议)UDP(用户数据报协议),两者的共同功能:寻址

TCP
TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
在这里插入图片描述
数据帧格式
(1)序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
(2)确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1
(3)标志位:共6个,即URGACKPSHRSTSYNFIN等,具体含义如下:
(A)URG:紧急指针(urgent pointer)有效。
(B)ACK:确认序号有效。
(C)PSH:接收方应该尽快将这个报文交给应用层。
(D)RST:重置连接。
(E)SYN:发起一个新连接。
(F)FIN:释放一个连接。

来自:https://blog.csdn.net/sssnmnmjmf/article/details/68486261

UDP
UDP(用户数据报协议)是一种面向事务、简单不可靠信息传送、基于数据报的传输层通信协议。
在这里插入图片描述
填充字节表示如果数据长度为奇数,数据就长度加1。

局域网中数据流动图:
在这里插入图片描述
数据流向:从应用程序开始,通过TCPIP的实现,通过网络,并在另一端通过IPTCP的实现进行备份。

TCPUDP使用端口号来确定主机内的应用程序。TCPUDP被称为端到端的传输协议,因为它们自始至终把数据从一个程序运送到另一个程序(而IP只把数据从一台主机运送到另一台主机)

TCP的可靠传输
TCP被设计成在IP提供的主机到主机信道中检测可能发生的丢失复制分组及其他错误,并从中恢复过来。TCP提供了可靠的字节流信道,因此应用程序不需要处理这些问题。它是面向连接的协议:在把它用于信道之前,两个程序必须先建立一条TCP连接,这涉及在两台通信的计算机上的TCP实现之间完成握手消息的交换。使用TCP在许多方面也类似于文件输入\ 输出I/O)。

TCP确保可靠传输的机制
(1)校验和
(2)序列号
(3)确认应答
(4)超时重传
(5)连接管理
(6)流量控制
(7)拥塞控制
其实都是计算机网络课教过的东西,如果忘记了,具体原理和过程可以参考:https://blog.csdn.net/liuchenxia8/article/details/80428157

TCP的三次握手建立连接和四次挥手断开连接原理可以参考:https://www.cnblogs.com/Andya/p/13291686.html

UDP的不可靠传输
TCP不同,UDP不会尝试从IP经历的错误中恢复,它只是扩展IP“尽力而为”的数据报服务,使得它在应用程序之间(而不是主机之间)工作。因此,使用UDP的应用程序必须准备好处理分组重新排序等问题的方法和预备程序。

客户与服务器
客户程序发起通信,而服务器程序则被动地等待,然后响应联系它的客户端。客户端与服务器一起组成了应用程序。客户与服务器描述了一种典型的情况,其中服务器使一种特定的能力(如数据库服务)可供能够与之通信的任何客户使用。

套接字:
套接字(socket)是一个抽象层,应用程序可以通过它发送和接收数据,其方式与打开文件句柄允许应用程序读、写数据到稳定的存储器非常相似。套接字允许应用程序插入到网络中,并与插入到同一网络中的其他应用程序通信。由一台机器上的应用程序写到套接字中的信息可以被不同机器上的应用程序读取,反之亦然。

以下部分关于套接字的资料来自:https://blog.csdn.net/daaikuaichuan/article/details/83061726

套接字的类型:流套接字(SOCK_STREAM):提供面向连接、可靠的数据传输服务,对应TCP。数据报套接字(SOCK_DGRAM):数据报套接字提供了一种无连接的服务,对应UDP。原始套接字(SOCK_RAW)。

三种套接字的区别:原始套接字可以读写内核没有处理的IP数据包,流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。如果要访问其他协议发送数据必须使用原始套接字。

套接字缓冲区
每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区输出缓冲区
write() / send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。反过来想,write()函数返回1并不代表发送成功,只是将数据写入缓冲区写入成功了。
read() / recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。
在这里插入图片描述
注:
(1)I/O缓冲区在每个TCP套接字中单独存在;
(2)I/O缓冲区在创建套接字时自动生成;
(3)即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
(4)关闭套接字将丢失输入缓冲区中的数据。

2、函数的使用:

以下大部分资料来自:http://c.biancheng.net/view/2344.html做了一些补充。

(1)socket()

函数原型:

int socket(int af,int type,int protocol);

作用:
创建套接字。

参数:
fa
af 为地址族(Address Family),也就是 IP 地址类型。常用的的协议簇:AF_INET(IPv4)AF_INET6(IPv6)AF_LOCAL(UNIX协议)AF_ROUTE(路由套接字)AF_KEY(秘钥套接字)

type
数据传输方式、套接字类型,常用的套接字的类型:SOCK_STREAM(字节流套接字、面向连接套接字)SOCK_DGRAM(数据报套接字、无连接的套接字)

protocol
标识传输协议,常用的有IPPROTO_TCP(TCP传输协议)IPPTPTP_UDP(UDP传输协议),如果此参数设为0,系统会自动识别使用的协议:TCP面向连接,UDP无连接,所以系统可以通过type参数识别协议。

使用:

//TCP (面向连接的)
int tcp_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
//UDP (无连接的)
int udp_sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);

windows中使用:

int SOCKET socket(int af, int type, int protocol);
(2)bind()

函数原型:

int bind(int sock, struct sockaddr *addr, socklen_t addrlen);

作用:
套接字绑定IP和端口。

参数:
sock
socket函数返回的套接字描述符。

addr
是指向本地IP地址的结构体指针。

addrlen
结构长度。

结构体:

struct sockaddr{
      unsigned short sa_family; //通信协议类型族AF_xx
      char sa_data[14];  //14字节协议地址,包含该socket的IP地址和端口号
  };
struct sockaddr_in{
      short int sin_family; //通信协议类型族
     unsigned short int sin_port; //端口号
      struct in_addr sin_addr; //IP地址
      unsigned char si_zero[8];  //填充0以保持与sockaddr结构的长度相同
  };
struct in_addr{
    in_addr_t  s_addr;  //32位的IP地址
};  

sockaddrsockaddr_in的长度相同,都是16字节,只是将IP地址和端口号合并到一起,用一个成员 sa_data表示。要想给 sa_data赋值,必须同时指明IP地址和端口号。sockaddr是一种通用的结构体,可以用来保存多种类型的IP地址和端口号,而 sockaddr_in是专门用来保存 IPv4地址的结构体。

用来存放ipv6地址的结构体:

struct sockaddr_in6 {
    sa_family_t sin6_family;  //(2)地址类型,取值为AF_INET6
    in_port_t sin6_port;  //(2)16位端口号
    uint32_t sin6_flowinfo;  //(4)IPv6流信息
    struct in6_addr sin6_addr;  //(4)具体的IPv6地址
    uint32_t sin6_scope_id;  //(4)接口范围ID
};

使用时的代码:

//创建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//创建sockaddr_in结构体变量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
serv_addr.sin_family = AF_INET;  //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址,本地
serv_addr.sin_port = htons(8000);  //端口
//将套接字和IP、端口绑定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

windows中使用:

int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);
(3)connect()

函数原型:

int connect(int sock,struct sockaddr *serv_addr,socklen_t addrlen);

作用:
建立到达服务器的连接。

参数:(参数与bind()一样)

sock
socket函数返回套接字描述符。

serv_addr
服务器IP地址结构指针。

addrlen
结构体指针的长度。

windows中使用:

int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen); 
(4)listen()

函数原型:

int listen(int sock, int backlog)

作用:
通过isten()函数可以让套接字进入被动监听状态。

参数:
sock
sock为需要进入监听状态的套接字。

backlog
设置可连接客户端的最大连接个数,即请求队列。当有多个客户端向服务器请求时,收到此值的影响。当设该参数为SOMAXCONN时,会让系统来决定请求队列的长度。

被动监听:
指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。

请求队列:
当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没法处理的,只能把它放进缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区,就称为请求队列(Request Queue)
当请求队列满时,就不再接收新的请求,对于 Linux,客户端会收到 ECONNREFUSED错误,对于 Windows,客户端会收到 WSAECONNREFUSED错误。

windows中的使用:

int listen(SOCKET sock, int backlog);

注意:
listen()只是让套接字处于监听状态,并没有接收请求。接收请求需要使用 accept()函数。

(5)accept()

函数原型:

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);

作用:
当套接字处于监听状态时,可以通过 accept()函数来接收客户端请求。

参数:
sock
服务器端套接字。

addr
sockaddr_in结构体变量。

addrlen
参数 addr的长度,可由 sizeof()求得。

windows中使用:

SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);

注:
listen()只是让套接字进入监听状态,并没有真正接收客户端请求,listen()后面的代码会继续执行,直到遇到 accept()accept()会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。

(6)write()

函数原型:

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

作用:
向套接字中写入数据。

参数:
fd
要写入的文件的描述符。

buf
要写入的数据的缓冲区地址。

nbytes
要写入的数据的字节数。

注:
两台计算机之间的通信相当于两个套接字之间的通信,在服务器端用 write()向套接字写入数据,客户端就能收到,然后再使用 read()从套接字中读取出来,就完成了一次通信。

windows中的使用:
windows下使用的是send()

int send(SOCKET sock, const char *buf, int len, int flags);
(7)read()

函数原型:

ssize_t read(int fd, void *buf, size_t nbytes);

作用:
read()函数会从 fd文件中读取 nbytes个字节并保存到缓冲区buf,成功则返回读取到的字节数(但遇到文件结尾则返回0),失败则返回 -1

参数:
fd
要读取的文件的描述符。

buf
要接收数据的缓冲区地址。

nbytes
要读取的数据的字节数。

windows中的使用:
windows下使用的是recv()

int recv(SOCKET sock, char *buf, int len, int flags);
(8)inet_addr()

函数原型:

in_addr_t inet_addr(const char* strptr);

作用:
将字符串形式的IP地址,转为网络字节序的整型。

参数:
字符串型地址,如:“192.168.1.5”

(9)inet_ntoa()

函数原型:

char *inet_ntoa(struct in_addr in);

作用:
将网络字节序的整型值转为字符串型的IP地址。

(10)memset()

函数原型:

extern void *memset(void *buffer, int c, int count)

作用:
使用某个值填充数组或指针。

参数:
buffer
等待填充的数组或指针。

c
填充的值。

count
buffer中填充的长度。

使用:

//把buffer数组,所有元素置0
memset(buffer, 0, sizeof(buffer))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值