《TCP/IP 网络编程》.((韩)尹圣雨)
socket是什么
网络编程
1.1、socket()
网络数据传输用的软件设备
int socket(int domain, int type, int protocol);
domain(协议族)
type(套接字类型,数据传输方式)
名称 | 套接字 |
---|---|
SOCK_STREAM | 流格式套接字TCP |
SOCK_DGRAM | 数据报格式套接字UDP |
- SOCK_STREAM (数据的完整送达,tcp发送数据,有ack)
- 数据在传输过程中不会消失;
- 数据是按照顺序传输的;
- 数据的发送和接收不是同步的(有的教程也称“不存在数据边界”)。主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的;发送可以分批30+50+20,接收放一次100个取走,也可能10个10个取等等
就像一个传送带,浏览器所使用的 http 协议就基于此,确保数据的完整送达,有答复ack
- SOCK_DGRAM (效率和实时)
- 强调快速传输而非传输顺序;
- 传输的数据可能丢失也可能损毁;
- 限制每次传输的数据大小;
- 数据的发送和接收是同步的(有的教程也称“存在数据边界”)。
像送快递的一样,确保快速送达,QQ聊天和语音就是这种
protocol(计算机间通信中使用的协议信息)
- 通常为0,前两个参数就能确定了;
- 除非同一协议族中存在多个数据传输方式相同的协议(?书上没有例子~)
1.2、sockaddr_in
struct sockaddr_in
{
sa_family_t sin_family; //地址族
uint16_t sin_port; //16位TCP/UDP端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero[8];//不适用
};
sin_family
这个和上面的协议族可以说是一对一的
sin_port
一台计算机可以同时提供多种网络服务,端口号就是为了区分不同网络服务
sin_addr
目前广泛使用 IPv4 地址,它的资源是非常有限的,一台计算机一个 IP 地址是不现实的,往往是一个局域网才拥有一个 IP 地址。
- INADDR_ANY,用于服务器的,自动获取运行服务器端的计算机IP地址(客户端,除非带有部分服务器端的功能,否则不会采用)
1.3、bind()
服务器端用于向套接字分配IP地址和端口号,只有这样,流经该 IP 地址和端口的数据才能交给套接字处理
int bind(int sock, struct sockaddr *addr, socklen_t addrlen); //Linux
int bind(SOCKET sock, const struct sockaddr *addr, int addrlen); //Windows
sockfd(套接字文件描述符,socket创建的返回值)
myaddr(存有创建的套接字地址信息的结构体)
addrlen(myaddr的长度)
1.4、connect()
用来建立连接,用于TCP
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen); //Linux
int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen); //Windows
sockfd(套接字文件描述符,socket创建的返回值)
myaddr(存有创建的套接字地址信息的结构体)
addrlen(myaddr的长度)
以上是两者都通用的,下面分析不同的
2、TCP
先创建服务器,达到listen(),这是客户端才能连接connect()服务器;存在客户端调用connect()前,服务器端有可能率先调用accept(),此时,服务器进入阻塞状态;
2.1、listen()
让套接字进入被动监听状态,此时客户端才能进入可发处连接请求,既是说,调用connect函数
int listen(int sock, int backlog); //Linux
int listen(SOCKET sock, int backlog); //Windows
sock(套接字文件描述符)
backlog(连接请求等待队列的长度,如5,则最多使5个连接请求进入队列)
2.2、accept()
响应客户端的请求
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen); //Linux
SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen); //Windows
sock(套接字文件描述符)
myaddr(存有客户地址信息的结构体)
addrlen(myaddr的长度)
2.3、读、写
因为TCP是需要连接,才能通信的,所以在发送和接收数据不需要另一方的地址信息
- 写数据,只是把数据写入到缓存里面(类似队列),具体什么时候发送,多大的数据才会发,有一个判断规则,所以导致接收方不知道头尾~
- 读数据,也是从缓存里面读取出来
- 当read()函数返回值为0时,表示对端已经关闭了 socket,这时候也要关闭这个socket,否则会导致socket泄露。netstat命令查看下,如果有closewait状态的socket,就是socket泄露了
ssize_t write(int fd, const void *buf, size_t nbytes);//Linux
ssize_t read(int fd, void *buf, size_t nbytes);//Linux
int send(SOCKET sock, const char *buf, int len, int flags);//Windows
int recv(SOCKET sock, char *buf, int len, int flags);//Windows
- sock 套接字
- buf 为要发送的数据的缓冲区地址
- len 为要发送的数据的字节数
- flags 为发送数据时的选项,一般为0或NULL
如何解决tcp粘包问题
我们可以把信息做成json,前面是包的基础信息(开放,不加密),后面是要发送的通信协议内容,协议内容可以进行加密
Server
#define TCP_LISTEN_PORT 9190
#define BUF_SIZE 30
static struct sockaddr_in serv_addr;
static struct sockaddr_in clnt_addr;
int tcp_socket_fd = -1;
void TCP_Server_Socket_Init(void)
{
tcp_socket_fd = socket(PF_INET, SOCK_STREAM, 0);
if(-1 == tcp_socket_fd )
{
printf("socket() error\n");
return;
}
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(TCP_LISTEN_PORT);
if(-1 == bind(tcp_socket_fd , (struct sockaddr*)&serv_addr, sizeof(serv_addr)))
{
printf("bind() error\n");
return;
}
if(-1 == listen(tcp_socket_fd , 5)
{
printf("listen() error\n");
return;
}
}
int main()
{
int fd_ret = 0;
struct timeval timeout;
fd_set fb_sockset;
char message[BUF_SIZE];
int srt_len;
int clnt_socket_fb;
socklen_t clnt_adr_sz;
TCP_Server_Socket_Init();
clnt_adr_sz = sizeof(clnt_adr);
while(1)
{
FD_ZERO(&fb_sockset);
FD_SET(tcp_socket_fd ,&fb_sockset);
timeout.tv_sec=2;
timeout.tv_usec=0;
fd_ret = select(tcp_socket_fd +1, &fb_sockset, NULL, NULL, &timeout);
if(fd_ret > 0)
{
clnt_socket_fb= accept(tcp_socket_fd , (struct sockaddr*)&clnt_addr, &clnt_adr_sz );
if(-1 == clnt_socket_fb)
{
printf("accept() error\n");
}
else
{
while(srt_len=recv(clnt_socket_fb, message, BUF_SIZE, 0)!=0)
{
send(clnt_socket_fb, message, srt_len, 0);
printf("Message from clent: %s",message);
close(clnt_socket_fb);
}
}
}
}
close(tcp_socket_fd );
return 0;
}
Client
#define TCP_LISTEN_PORT 9190
#define BUF_SIZE 30
static struct sockaddr_in serv_addr;
static struct sockaddr_in clnt_addr;
int tcp_socket_fd = -1;
void TCP_Client_Socket_Init(void)
{
tcp_socket_fd = socket(PF_INET, SOCK_STREAM, 0);
if(-1 == tcp_socket_fd )
{
printf("socket() error\n");
return;
}
memset(&clnt_addr, 0, sizeof(clnt_addr));
clnt_addr.sin_family = AF_INET;
clnt_addr.sin_addr.s_addr = inet_addr("192.168.0.1");//方式1
clnt_addr.sin_addr.s_addr = htonl(0xC0A80001);//方式2
clnt_addr.sin_port = htons(TCP_LISTEN_PORT );
if(-1 == connect(tcp_socket_fd, (struct sockaddr*)&clnt_addr, sizeof(clnt_addr))))
{
printf("connect() error\n");
}
else
{
printf("connected...............\n");
}
}
int main()
{
char message[]=“Nice.to.meet.you!”;
int srt_len;
socklen_t serv_adr_sz;
TCP_Client_Socket_Init();
serv_adr_sz = sizeof(serv_addr);
while(1)
{
send(tcp_socket_fd , message, strlen(message), 0, );
str_lent = recv(tcp_socket_fd , message, BUF_SIZE -1, 0);
printf("Message from server: %s",message);
}
close(tcp_socket_fd);
return 0;
}
3、UDP
3.1、读、写
因为UDP是不需要连接,就能通信的,所以在发送和接收数据需要另一方的地址信息
ssize_t sendto(int sock, const void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);//Linux
ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);//Linux
int send(SOCKET sock, const char *buf, int len, int flags);//Windows
int recv(SOCKET sock, char *buf, int len, int flags);//Windows
- sock 套接字
- buf 为要发送的数据的缓冲区地址
- len 为要发送的数据的字节数
- flags 为发送数据时的选项,一般为0或NULL
- to 目标地址信息的结构体
- addrlen to的长度
Server
#define UDP_LISTEN_PORT 9190
#define BUF_SIZE 30
static struct sockaddr_in serv_addr;
static struct sockaddr_in clnt_addr;
int udp_socket_fd = -1;
void UDP_Server_Socket_Init(void)
{
udp_socket_fd = socket(PF_INET, SOCK_DGRAM, 0);
if(-1 == udp_socket_fd )
{
printf("socket() error\n");
return;
}
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(UDP_LISTEN_PORT );
if(-1 == bind(udp_socket_fd , (struct sockaddr*)&serv_addr, sizeof(serv_addr)))
{
printf("bind() error\n");
return;
}
}
int main()
{
char message[BUF_SIZE];
int srt_len;
socklen_t clnt_adr_sz;
UDP_Server_Socket_Init();
clnt_adr_sz = sizeof(clnt_adr);
while(1)
{
str_lent = recvfrom(udp_socket_fd , message, BUF_SIZE, 0, (struct sockaddr*)&clnt_addr, &clnt_adr_sz );
sendto(udp_socket_fd, message, BUF_SIZE, 0, (struct sockaddr*)&clnt_addr, clnt_adr_sz );
printf("Message from client: %s",message);
}
close(udp_socket_fd);
return 0;
}
Client
#define UDP_LISTEN_PORT 9190
#define BUF_SIZE 30
static struct sockaddr_in serv_addr;
static struct sockaddr_in clnt_addr;
int udp_socket_fd = -1;
void UDP_Client_Socket_Init(void)
{
udp_socket_fd = socket(PF_INET, SOCK_DGRAM, 0);
if(-1 == udp_socket_fd )
{
printf("socket() error\n");
return;
}
memset(&clnt_addr, 0, sizeof(clnt_addr));
clnt_addr.sin_family = AF_INET;
clnt_addr.sin_addr.s_addr = inet_addr("192.168.0.1");//方式1
clnt_addr.sin_addr.s_addr = htonl(0xC0A80001);//方式2
clnt_addr.sin_port = htons(UDP_LISTEN_PORT );
}
int main()
{
char message[]=“Nice.to.meet.you!”;
int srt_len;
socklen_t serv_adr_sz;
UDP_Client_Socket_Init();
serv_adr_sz = sizeof(serv_addr);
while(1)
{
sendto(udp_socket_fd, message, strlen(message), 0, (struct sockaddr*)&clnt_addr, sizeof(clnt_addr) );
str_lent = recvfrom(udp_socket_fd , message, strlen(message), 0, (struct sockaddr*)&clnt_addr, &serv_adr_sz);
printf("Message from server: %s",message);
}
close(udp_socket_fd);
return 0;
}