1、什么是Socket(插座、套接字)
- IP:网络中能标识唯一一台主机;
- Port:主机中标识唯一一个进程;
- Socket:IP+Port;
- socket在Linux中为七种基本文件中的一个(普通-、目录d、连接l、管道p、字符设备c、块设备b、套接字s)
2、Socket的基本特性
- socket是成对出现,绑定ip+端口
- 一个文件描述符,两个缓冲区,全双工读写
3、C/S模型
- 大小端问题:
- 小端:低地址-存低位、高地址-存高位
- 大端:低地址-存高位、高地址-存低位
- 网络数据流应采用大端字节序,即低地址高字节
#include <arpa/inet.h> // h:host、n:network、l:表示32位长整数、s:表示16位短整数 uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
- ip地址相关转换函数
#include <arpa/inet.h> // 主机到网络 int inet_pton(int af, const char *src, void *dst); // 网络到主机 const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
4、Socket数据结构与相关t函数
// man 7 ip查看
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
socket() creates an endpoint for communication and returns a file descriptor that refers to that endpoint. The file descriptor returned by a successful call will be the lowest-numbered file descriptor not currently open for the process.
The domain argument specifies a communication domain; this selects the protocol family which will be used for communication. These families are defined in <sys/socket.h>. The currently understood formats include:
Name Purpose Man page
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device netlink(7)
AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK AppleTalk ddp(7)
AF_PACKET Low level packet interface packet(7)
AF_ALG Interface to kernel crypto API
The socket has the indicated type, which specifies the communication semantics. Currently defined types are:
SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported.
SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length).
SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with each input system call.
SOCK_RAW Provides raw network protocol access.
SOCK_RDM Provides a reliable datagram layer that does not guarantee ordering.
SOCK_PACKET Obsolete and should not be used in new programs; see packet(7).
Some socket types may not be implemented by all protocol families.
Since Linux 2.6.27, the type argument serves a second purpose: in addition to specifying a socket type, it may include the bitwise OR of any of the following values, to modify the behavior of socket():
SOCK_NONBLOCK Set the O_NONBLOCK file status flag on the new open file description. Using this flag saves extra calls to fcntl(2) to achieve the same result.
SOCK_CLOEXEC Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor. See the description of the O_CLOEXEC flag in open(2) for reasons why this may be useful.
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
// 同时建立连接的数(处于3次握手的链接数和)
int listen(int sockfd, int backlog);
// struct sockaddr *addr为传出参数,socklen_t*addrlen为传入传出参数,返回一个新的socket文件描述符
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// 连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
5、Socket通讯的基本步骤
- Server
- socket( ) —> bind( ) —> listen( ) —> accept( ) —> read( ) —> write( ) —> read( ) —> close( )
- Client
- socket( ) —> connect( ) —> write( ) —> read( ) —> close( )
6、示例Demo
server.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <strings.h>
#define SOCKET_PORT 3000
int main(){
// 1、socket
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd < 0){
perror("socket error");
exit(1);
}
// 2、bind
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(SOCKET_PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
int ret = bind(sfd, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0){
perror("bind error");
exit(1);
}
// 3、listen
ret = listen(sfd, 128);
if (ret < 0){
perror("listen error");
exit(1);
}
// 4、accept
struct sockaddr_in c_addr;
socklen_t addrlen = sizeof(struct sockaddr_in);
int cfd = accept(sfd, (struct sockaddr *)&c_addr, &addrlen);
// 打印客户端信息
char c_addr_s[BUFSIZ];
bzero(c_addr_s, sizeof(c_addr_s));
printf("Client:%s, port:%d\n",
inet_ntop(AF_INET, &c_addr.sin_addr.s_addr, c_addr_s, sizeof(c_addr_s)),
ntohs(c_addr.sin_port));
// 5、read
char buf[BUFSIZ];
while(1){
ssize_t n = read(cfd, buf, BUFSIZ);
// 5.1、处理
int i;
for (i = 0; i < n; i++){
buf[i] = toupper(buf[i]);
}
// 6、write
ret = write(cfd, buf, n);
if (ret < 0){
perror("write error");
exit(1);
}
}
// 7、close
close(sfd);
close(cfd);
return 0;
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#define SERVER_PORT 3000
#define SERVER_IP "10.211.55.6"
int main(){
// 1、socket
int sfd = socket(PF_INET, SOCK_STREAM, 0);
if (sfd < 0){
perror("socket error");
exit(1);
}
// 2、connect
// 创建套接字结构体
struct sockaddr_in svr_addr;
svr_addr.sin_family = PF_INET;
svr_addr.sin_port = htons(SERVER_PORT);
// 清0操作
memset(&svr_addr.sin_addr.s_addr, 0, sizeof(svr_addr.sin_addr.s_addr));
// 将点分十进制字符串转为字节序
inet_pton(PF_INET, SERVER_IP, &svr_addr.sin_addr.s_addr);
// 发起网络连接
int ret = connect(sfd, (struct sockaddr *)&svr_addr, sizeof(svr_addr));
if (ret < 0){
perror("connect error");
exit(1);
}else{
printf("服务器连接成功!\n");
}
char buf[BUFSIZ];
while(1){
// 读取输入流
fgets(buf, sizeof(buf), stdin);
// 写入套接字
write(sfd, buf, strlen(buf));
// 读取服务器响应
int n = read(sfd, buf, sizeof(buf));
// 将结果输出到标准输出流
write(STDOUT_FILENO, buf, n);
}
close(sfd);
return 0;
}
执行结果:
// 服务端
parallels@ubuntu:~/Linux/socket$ ./server.out
Client:10.211.55.2, port:59929
^C
parallels@ubuntu:~/Linux/socket$
// 客户端
yusian@SianMac2:~/Linux/socket$ ./client.out
服务器连接成功!
abc
ABC
hello world
HELLO WORLD
^C
yusian@SianMac2:~/Linux/socket$
- socket常用函数的封装
wrap.h
#IFNDEF _WRAP_H_
#DEFINE _WRAP_H_
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int Socket(int domain, int type, int protocol);
int Bind(int sockfd, const struct sockaddr_in *addr, socklen_t addrlen);
int Listen(int sockfd, int backlog);
int Accept(int sockfd, struct sockaddr_in *addr, socklen_t *addrlen);
int Connect(int sockfd, const struct sockaddr_in *addr, socklen_t addrlen);
#ENDIF
wrap.c
#include “wrap.h"
#include <errno.h>
void print_err(const char* descript)
{
perror(descript);
exit(1);
}
int Socket(int domain, int type, int protocol)
{
int ret = socket(domain, type, protocol);
if (ret < 1) print_err("socket error");
return ret;
}
int Bind(int sockfd, const struct sockaddr_in *addr, socklen_t addrlen)
{
int ret = bind(sockfd, (struct sockaddr *)addr, addrlen);
if (ret < 0) print_err("bind error");
return ret;
}
int Accept(int sockfd, struct sockaddr_in *addr, socklen_t *addrlen)
{
int sfd;
again:
sfd = accept(sockfd, (struct sockaddr *)addr, addrlen);
if (sfd < 0){
// 阻塞等待,考虑被信号中断的情况(慢速系统调用,在系统阻塞期间有可能被信号中断)
if ((errno == ECONNABORTED) || (errno == EINTR)){
goto again;
}else{
print_err("accept error");
}
}
return sfd;
}
int Listen(int sockfd, int backlog)
{
int ret = listen(sockfd, backlog);
if (ret < 0) print_err("listen error");
return ret;
}
int Connect(int sockfd, const struct sockaddr_in *addr, socklen_t addrlen)
{
int ret = connect(sockfd, (struct sockaddr *)addr, addrlen);
if (ret < 0) print_err("connect error");
return ret;
}
ssize_t Readn(int fd, void *buf, size_t count)
{
size_t readed = 0;// 已读数
size_t remain = count;// 未读数
while(remain > 0){
int n = read(fd, buf+readed, remain);
if (n < 0){ // 异常处理
if (errno == EINTR){
n = 0;// 信号中断,继续循环
}else{
return n;// 异常情况直接返回
}
}else if (n < remain){
remain -= n;
break;
}
readed += n;
remain -= n;
}
return count - remain;// 返回应读数-未读数
}
socket中读取数据read方法返回值分析
* > 0
:实际读到的字节数 buf = 1024 或buf < 1024但 > 0;
* = 0
:数据读完(读到文件、管道、socket 末尾—对端关闭);
* - 1
:异常
* errno = = EINTR 被信号中断 重启/quit
* errno = = EAGAIN(EWOULDBLOCK)非阻塞方式读,并且没有数据
* errno = = 其他值,出现错误:perror( ) exit( );
7、TCP建立连接三次握手、关闭连接四次握手
- SYN:代表请求创建连接,所以在三次握手中前两次要SYN=1,表示这两次用于建立连接,至于第三次什么用,在疑问三里解答。
- FIN:表示请求关闭连接,在四次分手时,我们发现FIN发了两遍。这是因为TCP的连接是双向的,所以一次FIN只能关闭一个方向。
- ACK:代表确认接受,从上面可以发现,不管是三次握手还是四次分手,在回应的时候都会加上ACK=1,表示消息接收到了,并且在建立连接以后的发送数据时,都需加上ACK=1,来表示数据接收成功。
- seq:序列号,什么意思呢?当发送一个数据时,数据是被拆成多个数据包来发送,序列号就是对每个数据包进行编号,这样接受方才能对数据包进行再次拼接。初始序列号是随机生成的,这样不一样的数据拆包解包就不会连接错了。(例如:两个数据都被拆成1,2,3和一个数据是1,2,3一个是101,102,103,很明显后者不会连接错误)
- ack:这个代表下一个数据包的编号,这也就是为什么第二请求时,ack是seq+1,
注意:ACK与ack
7.1、MTU&MSS
- MTU:Maxitum Transmission Unit 最大传输单元
- MSS:Maxitum Segment Size 最大分段大小
- WIN:滑动窗口,当前本端能接收的数据上限值(单位:字节)
-
MSS加包头数据就等于MTU. 简单说拿TCP包做例子。 报文传输1400字节的数据的话,那么MSS就是1400,再加上20字节IP包头,20字节tcp包头,那么MTU就是1400+20+20. 当然传输的时候其他的协议还要加些包头在前面,总之MTU就是总的最后发出去的报文大小。MSS就是你需要发出去的数据大小。
-
为了达到最佳的传输效能TCP协议在建立连接的时候通常要协商双方的MSS值,这个值TCP协议在实现的时候往往用MTU值代替(需要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes)所以往往MSS为1460。通讯双方会根据双方提供的MSS值得最小值确定为这次连接的最大MSS值。