一、前言
小结::
- 网络编程,可以建立多机通讯,依靠的是地址,
地址:指的是下面的两个IP地址
端口号
- 数据交流:协议(http,tcp/UDP…)
- 单片机与PC机靠的是串口协议进行数据交流
- Linux的协议,TCP/UDP
- TCP/UDP的对比
端口号作用
- 一台拥有IP地址的主机可以提供许多的服务(比如,web服务,FTP服务、SMTP服务)这些可以通过一个IP地址来实现
- 那么主机是怎么样区分不同的网络服务的呢?
- 单独靠IP地址是不够的,因为IP地址与网络服务的关系是一对多
- 得通过“IP地址 + 端口号”来区分不同的服务
- 端口号提供一种访问通道
- 服务器一般都是通过端口号来识别 (例:对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,Telent服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69)
字节序(点击详解)
- Little endian(LE):将低序字节存储在起始地址
- Big endian(DE):将高序字节存储在起始地址
例子:在内存中双字0x01020304(DWORD)的存储方式
内存地址
4000&4001&4002&4003
LE 04 03 02 01
BE 01 02 03 04
Socket服务器和客户端的开发步骤
API:
1、int socket(int domain, int type, int protocol); //创建通信端点
2、int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);//用于绑定IP地址和端口号到socket
3、int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);//在套接字上发起一个连接
4、int listen(int sockfd, int backlog);//监听套接字上的连接
5、int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//接受套接字上的连接
原型:socket
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
如果成功,将返回新套接字的文件描述符。
如果出现错误,则返回−1,并适当设置errno。
-
domin:
域参数指定通信域;这将选择用于通信的协议族,通常为AF_INET
表示互联网协议族(TCP/IP协议族
)- AF_UNIX, AF_LOCAL 局部通信
- AF_INET IPv4 因特网域
- AF_INET6 IPv6 因特网域
- AF_UNIX Unix 域
- AF_NETLINK 内核用户界面设备
- AF_ROUTE 路由套接字
- AF_KEY 密钥套接字
- AF_UNSPEC 未指定
-
type参数指定socket的类型:
- SOCK_STREAM:
它使用TCP
协议,提供顺序的、可靠的、双向的、基于连接的字节流。可能支持带外数据传输机制。 - SOCK_DGRAM:
它使用数据报协议UDP
,支持数据报(固定最大长度的无连接、不可靠的消息)。 - SOCK_RAW:
允许程序使用底层协议,原始套接字允许对底层协议,如IP
或ICMP
进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。
- SOCK_STREAM:
-
protocol:
通常赋值为0
- 0 选择 type 类型对应的默认协议
- IPPROTO_TCP TCP传输协议
- IPPROTO_UDP UDP传输协议
- IPPROTO_SCTP SCTP传输协议
- IPPROTO_TIPC TIPC传输协议
原型: bind
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
struct sockaddr {
sa_family_t sa_family;//协议族
char sa_data[14];//IP + 端口号
}
等同于
struct sockaddr_in {
__kernel_sa_family_t sin_family; //Address family (协议族)
__be16 sin_port; // Port number(端口号)
struct in_addr sin_addr; // Internet address(IP地址)
unsigned char sin_zer0[8];//填充 没有实际意义,只是为了跟sockadd人结构体在内存中对齐,这样两者才能相互转换
};
struct in_addr {
__be32 s_addr;
};
如果成功,则返回0。
如果出现错误,则返回−1,并适当设置errno。
注:
地址转换为API
- sockfd:
函数socket的返回值(描述符) - addr:
结构体 - addrlen:
结构体的大小(sizeof)
原型: connect
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
如果连接或绑定成功,则返回0。
如果出现错误,则返回−1,并适当地设置errno。
原型: listen
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
如果连接或绑定成功,则返回0。
如果出现错误,则返回−1,并适当地设置errno。
sockfd
:
sockfd参数是一个文件描述符,它指向SOCK_STREAM或SOCK_SEQPACKET类型的套接字。backlog
backlog参数定义了sockfd的等待连接队列可能增长到的最大长度。如果一个连接请求在队列满时到达,客户端可能会收到一个指示ECONNREFUSED的错误,或者,如果底层协议支持重传,该请求可能会被忽略,以便稍后重新尝试连接时成功。
功能:
- 设置能处理的最大连接数,listen()并未开始接受连线,只是设置socket的listen模式,listen函数只用于服务器端,服务器进程不知道要与谁连接,因此,他不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。主要就两个功能:将一个未连接的套接字转换为一个被动套接字监听,规定内核为相应套接字排队的最大连接数。
- 内核为任何一个给定监听套接字维护两个队列:
- 未完连接队列,每个这样的
SYN
报文段对应其中一项: 已有某个客户端发出并到达服务器,而服务器正在等待完成相应的TCP
三次握手过程,这些套接字处于SYN_REVD
状态。 - 已完成连接队列,每个已完成
TCP
三次握手过程的客户端对应的其中一项。这些套接字处于ESTABLISHED
状态;
- 未完连接队列,每个这样的
原型:accept
- accept()系统调用用于基于连接的套接字类型(SOCK_STREAM, SOCK_SEQ-PACKET)。它提取侦听套接字(sockfd)的挂起连接队列上的第一个连接请求,创建一个新的连接套接字,并返回一个引用该套接字的新文件描述符。新创建的socket没有处于监听状态。处理步骤原始套接字sockfd不受此调用的影响
accept
函数由TCP
服务器调用,用于从已完成连接队列头返回下一个已完成连接,如果已完成连接队列为空,那么进程被投入睡眠。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
如果成功,这些系统调用将返回一个非负整数(新的套接字描述符),
已连接的套接字的描述符。
如果出现错误,则返回−1,并适当设置errno。
- sockfd:
sockfd参数是socket(2)创建的套接字,通过bind(2)绑定到本地地址,并在listen(2)之后监听连接。 - addr:
用来返回已连接的对端(客户端)的协议地址 - addrlen:
客户端地址长度
返回值:
- 该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个有服务器进程接收的客户连接创建一个已连接套接字(表示
TCP
三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。