传输层协议TCP
面向连接 可靠的 字节流服务
面向连接: 网络中的两个主机交互数据之前,必须先建立连接
可靠的: 数据够安全的、完的传输到对方。数据不丢失 数据不出错 数据不乱序 数据不重复
字节流服务: 数据没有发送与接收界限。发送方发送数据的次数与接收方接收数据的次数没有什么关系,如果接收端次未将数据读取完成,未读取的数据会继续保存在接缓冲区中
TCP协议的编程流程:
C/S模型 客户端服务器
服务器:socket(创建套接字) bind(命名) listen accept(接受) recv/send(发送数据) close(关闭链接)
客户端:socket(创建套接字) connect(建链接) send/recv(发送数据) close(关闭链接)
客户端和服务器端可以处在两个不同的主机上
#include <sys/types.h>
#include <sys/socket.h>
int socket (int domain, int type, int protocol); //创建 socket //套接字:通过网络发送数据
返回值:-1 出错
成功返回 socket 文件描述符
domain:协议簇 AF_INET
type:具体协议类型
TCP: SOCK_STREAM
UDP: SOCK_UGRAM
protocol: 下层具体协议 TCP/IP 默认给 0
绑定
int bind (int listenfd, struct sockaddr* seraddr, socklen_t len);
listenfd:socket 返回的文件描述符
seraddr:服务器端的 IP 地址‘
len:指定 IP 地址 的长度
返回值:失败返回 -1,成功返回0
失败的原因: 1、IP地址不对
2、端口号被占用或者没有权限使用此端口号
struct sockaddr_in
{
sa_family sin_family;//地址族
u_int16_t sin_port;//端口号:主机字节序 网络字节序
struct in_addr sin_addr;//IP地址:字符串形式的点分十进制---》整型值
}
struct in_addr
{
u_int32_t s_addr;//IPV4地址,要用网络字节序表示
}
1、字节序列
主机字节序:
大端模式:高位存低地址
小端模式:高位存高地址
网络字节序:大端模式
2、套接字地址:ip+端口
端口号 0 - 65535
0 - 1024 普通用户无法使用
1024 - 5000 保留
5000 以上可以使用
启动监听
int listen (int listenfd, int size);
size:监听队列的大小
两个队列:已完成连接的队列 +1
正在完成连接的队列
size 指定内核维护的等待处理的连接队列的大小,经典的值:5
int accept (int listenfd, struct sockaddr* cli_addr, socklen_t *len)
cli_addr:客户端的 IP 地址不用固定
失败返回 -1
成功返回维护本次连接的文件描述符 —>大于等于0
int recv (int fd, void *buff, size_t size, int flag); //read
fd:客户端文件描述符
buff:读取的数据
size:一次性读取的数据的个数
int send (int fd, void *buff, size_t size, int flag); //write
int close (int fd);
int connect (int sockfd, struct sockaddr *ser_addr, socklen_t len);
ser_addr:服务器的地址和端口号
返回值:-1 连接不成功
建立连接: sockfd 客户端的 socket 套换宇
ser_addr 服务端的地址信息 和哪一个服务器建立连接
len addr 的长度
cli
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>//字节序列转换函数所用
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>//地址转换函数所用
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(-1!=sockfd);
struct sockaddr_in ser;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
inet_aton("127.0.0.1",(struct in_addr*)&ser.sin_addr);
int res=connect(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(-1!=res);
while(1)
{
printf("please input: ");
fflush(stdout);
char data[128]={0};
fgets(data,128,stdin);
if(strncmp(data,"bye",3)==0)
{
close(sockfd);
break;
}
send(sockfd,data,strlen(data)+1,0);
char buff[128]={0};
int n=recv(sockfd,buff,127,0);
if(n<=0)
{
printf("error\n");
close(sockfd);
break;
}
printf("n==%d: %s\n",n,buff);
}
}
server
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main()
{
int listenfd=socket(AF_INET,SOCK_STREAM,0);
assert(-1!=listenfd);
struct sockaddr_in ser,cli;
memset(&ser,0,sizeof(ser));//清空
ser.sin_family=AF_INET;//IPv4地址族
ser.sin_port=htons(6000);//主机到网络的short型,给服务器指定端口号
ser.sin_addr.s_addr=inet_addr("127,0,0,1");//把字符型地址转换为整形
//绑定 命名 出错:1、IP地址不正确 2、端口号被占用或者没有权限
int res=bind(listenfd,(struct sockaddr*)&ser,sizeof(ser)); //将服务器的IP地址、端口号确定,让客户端访问
assert(-1!=res);
listen(listenfd,5);
while(1)
{
int len=sizeof(cli);
//c特定客户端和服务器连接的文件描述符
//accept函数只有在有客户端连接的情况下返回
int c=accept(listenfd,(struct sockaddr*)&cli,&len);//阻塞运行
assert(-1!=c);
while(1)
{
char buff[128]={0};
int n=recv(c,buff,127,0);//阻塞 有数据可读或者客户端退出都会返回
if(n==0)
{
printf("client unlink\n");
close(c);
break;//要继续接收下一个连接
}
else if(n==-1)
{
printf("error\n");
close(c);
break;
}
printf("n==%d: %s",n,buff);
send(c,"OK",2,0);
}
}
close(listenfd);
}