linux c socket编程学习(1)
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了linux c socket编程学习(1),小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含10196字,纯文字阅读大概需要15分钟。
内容图文
![linux c socket编程学习(1)](/upload/InfoBanner/zyjiaocheng/930/9a7042ca03a94903975efdd6fdb9beae.jpg)
1、相关理论:
大部分资料来自TCP/IP sockets编程(C语言实现)
,额外做了补充。
概念:
IP
:
每台主机的IP
地址都是不同的。主机的IP地址
有公网IP
和内网IP
。在局域网中,每台主机的IP
都取自192.168.1.1-192.168.1.255
(C类
)之间,不存在两台主机的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
个,即URG
、ACK
、PSH
、RST
、SYN
、FIN
等,具体含义如下:
(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。
局域网中数据流动图:
数据流向
:从应用程序开始,通过TCP
和IP
的实现,通过网络,并在另一端通过IP
和TCP
的实现进行备份。
TCP
和UDP
使用端口号来确定主机内的应用程序。TCP
和UDP
被称为端到端的传输协议,因为它们自始至终把数据从一个程序运送到另一个程序(而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地址
};
sockaddr
和 sockaddr_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)memst()
函数原型:
extern void *memset(void *buffer, int c, int count)
作用:
使用某个值填充数组或指针。
参数:
buffer
:
等待填充的数组或指针。
c
:
填充的值。
count
:
buffer
中填充的长度。
使用:
//把buffer数组,所有元素置0
memset(buffer, 0, sizeof(buffer))
内容总结
以上是互联网集市为您收集整理的linux c socket编程学习(1)全部内容,希望文章能够帮你解决linux c socket编程学习(1)所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。