目录
TCP特点
都是传输层协议,全双工通信、 面向连接、可靠
- 是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)
TCP适用情况
- 适合于对传输质量要求较高,以及传输大量数据的通信。
- 在需要可靠数据传输的场合,通常使用TCP协议
- MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议
TCP 通信流程
函数接口
socket
NAME:socket - create an endpoint for communication
SYNOPSIS://头文件
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能: 创建套接字文件
参数:
domain:协议族
AF_UNIX, AF_LOCAL 用于本地通信
AF_INET IPv4 Internet protocols
AF_INET6 IPv6 Internet protocols
type:协议类型
SOCK_STREAM TCP
SOCK_DGRAM UDP
SOCK_RAW 原始套接字
protocol:
一般情况下写0
系统默认自动帮助匹配对应协议
传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP
网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)
返回值:
成功: 返回一个特殊文件描述符;
失败: -1 更新errno
bind
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能: 绑定,将socket()返回值和IP/端口号进行绑定;
(以什么样的形式去绑定?就是填充第二个结构体,把端口号和IP填充到这个结构体中)
参数:
sockfd: 是socket()函数的返回值;
const struct sockaddr *addr:
struct sockaddr是结构体类型,是一个通用结构体;
struct sockaddr {
sa_family_t sa_family; // 2个字节typedef unsigned short int sa_family_t; //
char sa_data[14]; // 14字节
}
整个结构体大小为16个字节
(程序员每次填充的时候填充自己的结构体,将自己的结构体强转成通用的结构体,原因:每个协议都对应一个结构体,如果每个协议都调用一次这个函数,就会调用很多函数接口。为了做到统一,你可以定义自己的结构体。
用IPv4通信时传值需传对应结构体struct sockaddr_in是Internet的结构体,本地通信还会有本地通信所要填充的结构体sockaddr_un,每种协议都有自己需要填充的一个结构体,如果每种协议都有自己的函数接口的话,函数接口太多,没办法记忆,为了做到统一性,填充的填充自己的结构体,传值的时候传struct sockaddr,那么就需要把自己填充的sockaddr_in强制转换成struct sockaddr形式)
struct sockaddr_in {
unsigned short sin_family; //协议IPv4,2个字节
unsigned short sin_port; //端口号 ,2个字节 struct in_addr sin_addr;
struct in_addr {
__be32 s_addr;//IP地址 };
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
//8个字节
};
addrlen:
结构体的大小;
sizeof(serveraddr);
返回值:0
-1 失败,更新errno
listen
int listen(int sockfd, int backlog);
功能: 用于监听,将主动套接字变为被动套接字;
参数:
sockfd: socket()的返回值
backlog:客户端同时连接服务器的最大个数;
不同平台可同时链接的数不同,一般写6-8个
(队列1:保存正在连接)
(队列2,连接上的客户端)
返回值:
失败 -1
recv
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能: 接收数据
参数:
sockfd: acceptfd ;
buf 存放位置
len 大小
flags 一般填0,相当于read()函数
MSG_DONTWAIT 非阻塞
返回值:
< 0 失败出错
==0 表示客户端退出
>0 成功接收的字节个数
connect
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:用于连接服务器;
参数:
sockfd:socket函数的返回值
addr:
填充的结构体是服务器端的;
addrlen:
结构体的大小
返回值
-1 失败
正确 0
send
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:发送数据
参数:
sockfd:socket函数的返回值
buf:发送内容存放的地址
len:发送内存的长度
flags : 如果填0,相当于write();
TCP流程
服务器端
流程
1) socket(),创建套接字文件,用于连接 sockfd(有一个属性默认是阻塞)
2) bind(), 绑定,把socket()函数返回的文件描述符和IP、端口号进行绑定;
3) listen(), (监听)将socket()返回的文件描述符的属性,由主动变为被动;
4) accept(), 阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,
则accept()函数返回,返回一个用于通信的套接字文件;
5) recv(), 接收客户端发来的数据; read()
6) send(), 发送数据;
7) close(), 关闭文件描述符;连接、通信
代码演示
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int sockfd, acceptfd;
if (argc != 2)
{
printf("please input %s <port>\n", argv[0]);
return -1;
}
//1.创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err.");
return -1;
}
//填充结构体
struct sockaddr_in serveraddr, clientaddr;
serveraddr.sin_family = AF_INET;//IPV4
serveraddr.sin_port = htons(atoi(argv[1]));//从终端输入端口
//serveraddr.sin_addr.s_addr = inet_addr(argv[2]);//从终端输入ip
//serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);//"0.0.0.0"自动获取本机地址
serveraddr.sin_addr.s_addr = inet_addr("0.0.0.0"); //"0.0.0.0"自动获取本机地址
socklen_t len = sizeof(clientaddr);
//2.绑定
if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("bind err");
return -1;
}
//3.监听
if (listen(sockfd, 5) < 0)
{
perror("listen err.");
return -1;
}
//4.阻塞等待客户端链接
while (1)
{
if ((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len)) < 0)
{
perror("accept err.");
return -1;
}
printf("client:ip=%s,port=%d\n", inet_ntoa(clientaddr.sin_addr),
ntohs(clientaddr.sin_port));
char buf[128];
int recvbyte;
while (1)
{
recvbyte = recv(acceptfd, buf, sizeof(buf), 0);
if (recvbyte < 0)
{
perror("recv err.");
return -1;
}
else if (recvbyte == 0)
{
printf("client exit.\n");
break;
}
else
{
printf("buf=%s\n", buf);
}
}
close(acceptfd);
}
close(sockfd);
return 0;
}
客户端
流程
1) socket(),创建套接字文件,既用于连接,也用于通信;
完成一个结构体的填充
2) connect(); 用于发起连接请求;
3) send(), 发送数据;
4) recv(), 接收数据;
5) close(), 关闭文件描述符;
代码演示
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int sockfd,acceptfd;
if(argc != 3)
{
printf("please input %s <ip> <port>\n",argv[0]);
return -1;
}
//1.创建套接字
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("socket err.");
return -1;
}
//填充结构体
struct sockaddr_in serveraddr;
serveraddr.sin_family=AF_INET;//与服务器端一致选择IPV4
serveraddr.sin_port=htons(atoi(argv[2]));//与服务器端一致,选择相同端口
serveraddr.sin_addr.s_addr=inet_addr(argv[1]);//终端输入地址
//2.请求链接服务器
if(connect(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr))<0)
{
perror("bind err");
return -1;
}
char buf[128];
int recvbyte;
while(1)
{
fgets(buf,sizeof(buf),stdin);
if(buf[strlen(buf)-1]=='\n')
buf[strlen(buf)-1]='\0';//fgets获取\n也存储,故要把\n转换成\0,表示字符串结束。
send(sockfd,buf,sizeof(buf),0);
}
close(sockfd);
return 0;
}