源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。
它是网络通信过程中端点的抽象表示,包含进行网络通信必需的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
理解为用于网络编程结构体,设置端口连接 。使用文件描述符操作。
流套接字(SOCK_STREAM):
流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。
数据报套接字(SOCK_DGRAM):
数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。
原始套接字(SOCK_RAW):
原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW
原始套接字与标准套接字(标准套接字指流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。
1、创建socket用于接受网络连接请求:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//domain 地址族,用于说明套接字的具体用途,AF_INET
//type 套接字类型, SOCK_STREAM,SOCK_DGRAM
//protocol 指定协议类型,常用默认0
//返回值:成功,流式套接字的文件描述符;失败,-1
套接字占用文件描述符资源,打开多了就连不上了。
2.1、用于服务器端,绑定IP和端口
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//sockfd 待绑定地址信息的套接字描述符
//sockaddr 待绑定地址结构体的起始地址
//addrlen 待绑定地址结构体大小
//返回值:成功,返回0;失败,返回-1
2.2、用于客户端,建立TCP连接
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//connect 函数通常用于客户端建立tcp连接。
//sockfd: 标识一个套接字。
//serv_addr: 套接字s想要连接的主机地址和端口号。
//addrlen:name 缓冲区的长度。
//返回值:成功则返回0,失败返回-1
connect操作之后代表对应的套接字已连接,UDP协议在创建套接字之后,可以同多个服务器端建立通信,而TCP协议只能与一个服务器端建立通信,TCP不允许目的地址是广播或多播地址,UDP允许。当然UDP协议也可以像TCP协议一样,通过connect来指定对方的ip地址、端口。
struct sockaddr结构类型是用来保存socket信息的:
struct sockaddr
{
unsigned short sa_family;//一般为AF_INET,代表Internet(TCP/IP)地址族
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 sin_zero[8]; // 填充0以保持与struct sockaddr同样大小
};
//保存以十六进制表示的IP地址
struct in_addr
{
unsigned long s_addr;//in_addr_t s_addr
};
3、改变socket状态为监听状态
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
//sockfd 待改变状态的套接字描述符
//backlog 等待连接的队列最大单体个数。
//返回值:成功返回0.失败-1
4、接受客户端发送的连接请求
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//sockfd 待监听的套接字描述符
//addr 用于接收客户端的连接地址
//addrlen 客户端地址的长度,在调用accept()之前,必须有长度值,
调用完成之后传出一个实际长度值
//返回值:成功,返回连接套接字的文件描述符;失败,-1
5、发送数据
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
//sockfd 使用sockfd所关联的套接字发送数据
//buf 待发送数据的起始地址
//len 待发送数据的字节长度
//flags 旗标,发送方式
//返回值:实际发送成功的字节数
6、读取数据
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//sockfd 从sockfd所关联的套接字接受数据
//buf 存放接收数据的空间的起始地址
//len 存放空间的最大字节长度
//flags 旗标,接收方式
//返回值:实际发送成功的字节数
记录主机的信息结构体
struct hostent
{
char *h_name; //official name of host
char **h_aliases; //alias list */主机的别名,以NULL做为结束标记
int h_addrtype; //host address type
int h_length; //length of address
char **h_addr_list; //list of addresses
//是个字符数组指针,存放二进制地址,表示主机的ip地址,是以网络字节序存储的。
//不能直接用printf带%s参数来它,需要调用inet_ntop()。
//指针指向空间,空间内为结构体
}
#define h_addr h_addr_list[0] /* for backward compatibility */
关于ip地址和dots-and-number字符串之间的转换有若干个函数:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in);
struct in_addr inet_makeaddr(int net, int host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
int inet_pton(int af, const char *src, void *dst);
并发服务器伪代码
多线程版:
建立一次连接,就创建一个线程去处理该连接,主线程永远负责监听套接字的处理。创建线程消耗内存少,创建简单,使用简单
socket
bind
listen
while()
{
connfd == accept;
pthread_create(NULL, thread_func,connfd);
}
多进程版:
每建立一次连接,就创建一个进程去处理该连接,父线程永远负责监听套接字的处理。
socket
bind
listen
while()
{
connfd == accept;
if((pid = fork()) == 0)
{
处理当前连接
}
}
I/O模型
#######阻塞式I/O
数据到达,从外设或网络到内核(需要等待),从内核读走/复制走。函数只要调一次,但耗时效率低。只能看一个描述符,其他等待,有前后关系时使用。读发线程,收线程
#######非阻塞式I/O
频繁调用,轮询问。
#######信号驱动I/O
信号通知
#######异步I/O
时间上无序无干扰,需要内核支持
#######I/O复用
解决内存开销问题,并发数上线,K10C问题。同时监控多个描述符,机制下决定谁准备好了拿来用,筛选,处理。