套接字:基于TCP/IP协议可实现基本网络通信功能的逻辑对象。
机器与机器的通信,或者进程与进程的通信,在这里都可以被抽象地看作是套接字
与套接字的通信。
应用程序编写者无需了解网络协议中的任何细节,更无需知晓系统内核和网络设备的
运作机制,只要把像发送的数据写入套接字,或从套接字中读取想接收的数据即可。
TCP协议:三次握手,四次挥手
TCP保证数据传输的可靠性:
TCP的协议栈底层在向另一端发送数据时,会要求对方在一个给定的时间窗口内返回确认。
如果超过了这个时间窗口仍没有收到确认,则TCP会重传数据并等待更长的时间。只有在
数次重传均失败以后,TCP才会最终放弃。TCP含有用于动态估算数据往返时间的算法,因此
他知道等待一个确认需求时间。
TCP保证数据传输的有序性:
TCP的协议栈底层在向另一端发送数据时,会为所发送数据的每一个字节指定一个序列号。
即使这些数据字节没有能够按照发送时的顺序到达接收方,接收方的TCP也可以根据他们
的序列号重新排序,再把最后的结果交给应用程序。
TCP是全双工的:
在给定的连接上,应用程序在任何时候即可以发送数据也可以接收数据。因此,TCP必须跟踪
每个方向数据流的状态信息,如序列号和通告窗口的大小。
#include<sys/scoket.h>
int scoket(int domain,int type,int protocol)
功能:创建套接字
参数:domain:通信域,协议族,可取以下值:
PF_LOCAL/PF_UNIX 本地套接字,进程间通信
PF_INET 基于IPv4的网络通信
PF_INET6 基于IPv6的网络通信
PF_PACKET 基于底层包的网络通信
type:套接字类型,取值如下:
SOCK_STREAM 流式套接字,基于TCP协议
SOCK_DGRAM 数据报套接字,基于UDP协议
SOCK_RAW 原始套接字,工作在传输层以下
protocol:特殊协议,对于流式和数据报套接字而言,只能取0
返回值:成功返回表示套接字对象的文件描述符,失败返回-1.
基本地址结构,本身没有意义,仅用于泛型化参数
struct sockaddr
{
sa_family_t sa_family;//地址族
char sa_data[14];//地址值
};
本地地址结构,用于AF_LOCAL/AF_UNIX域的本地通信
struct sockaddr_un
{
sa_family_t sun_family;//地址族(AF_LOCAL/AF_UNIX)
char sun_path[];//本地套接字文件的路径
};
网络地址结构,用于AF_INET域的IPv4网络通信
struct sockaddr_in
{
sa_family_t sin_family;//地址族(AF_INET)
int_port_t sin_port;//端口号(0~65535)
struct in_addr sin_addr;//IP地址
};
struct in_addr
{
in_addr_t s_addr;
};
typedef uint16_t in_port_t;//无符号16位整数
typedef uint32_t in_addr_t;//无符号32位整数
字节序转换(大小端问题):
uint32_t htonl(uint32_t hostlong);//长整型主机字节序到网络字节序
uint32_t ntohl(uint32_t netlong);//长整型网络字节序到主机字节序
uint16_t htons(uint16_t hostshort);//短整型主机字节序到网络字节序
uint16_t ntohs(uint16_t netshort);//短整型网络字节序到主机字节序
in_addr_t inet_addr(char const* ip);//点分十进制字符串地址->网络字节序形式整数地址
int inet_aton(char const*ip,struct in_addr*nip);//点分十进制字符串地址->网络字节序形式整数地址
char* inet_ntoa(struct in_addr nip);//网络字节序形式整数地址->点分十进制字节序地址
绑定地址:
#include<sys/socket.h>
int bind(int sockfd,struct sockaddr const*addr,socklen_t addrlen);
功能:将套接字和本机的地址结构绑定在一起
参数:sockfd:套接字描述符
addr:自己的地址结构
addrlen:地址结构的字节数
返回值:成功返回0,失败返回-1。
启动监听:
#include<sys/socket.h>
int listen(int sockfd,int backlog)
功能:启动侦听:在指定套接字上启动连接请求的侦听功能
参数:sockfd:套接字描述符,在调用此函数之前是一个主动套接字,是不能
感知连接请求的,在调用此函数并成功返回后,是一个被动套接字,具有感知
连接请求的能力。
backlog:未决连接请求队列的最大长度,一般取不小于1024的值。
返回值:成功返回0,失败返回-1。
请求连接:
#include<sys/socket.h>
int connect(int sockfd,struct sockaddr const*addr,socklen_t addrlen);
功能:将套接字和对方的地址结构连接在一起
参数:codkfd:套接字描述符
addr:对方的地址结构
addrlen:地址结构字节数
返回值:成功返回0,失败返回-1。
等待连接:
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr* addr,socklen_t*addrlen);
功能:等待并接受连接请求,在指定套接字上阻塞,直到连接建立完成
参数:sockfd:侦听套接字描述符
addr:输出连接请求发起方的地址信息。
addrlen:输出连接请求发起方的地址信息字节数
返回值:成功返回可用于后续通信的连接套接字描述符,失败返回-1.
发送数据:
#include<sys/socket.h>
ssize_t send(int sockfd, void const*buf,size_t count,int flags);
功能:发送数据
参数:若flags取0则与write函数完全等价,另外也可取以下值:
MSG_DONTWAIT 以非阻塞方式接收数据
MSG_OOB 接收带外数据
MSG_DONTROUTE 不查路由表,直接在本地网络中寻找目的主机
返回值:成功返回实际发送字节数,失败返回-1;
接收数据
#include<sys/socket.h>
ssize_t recv(int sockfd, void *buf,size_t count,int flags);
功能:接收数据
参数:若flags取0则与read函数完全等价,另外也可取以下值:
MSG_DONTWAIT 以非阻塞方式接收数据
MSG_OOB 接收带外数据
MSG_WAITALL 等待所有数据,即不接受到count字节就不返回
返回值:成功返回实际接收字节数,失败返回-1;
//基于tcp协议的服务器
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<errno.h>
#include<signal.h>
#include<sys/wait.h>
//信号处理函数
void sigchild(int signum)
{
for(;;)
{
pid_t pid = waitpid(-1,NULL,WNOHANG);//非阻塞方式回收子进程
if(pid == -1)
{
if(errno == ECHILD)
{
printf("没有子进程可回收\n");
break;
}
else
{
perror("waitpid");
exit(-1);
}
}
else if(pid == 0)
{
printf("子进程在运行,没法收\n");
break;
}
else
{
printf("回收%d进程僵尸\n",getpid());
}
}
}
int main(void)
{
//捕获17号信号
if(signal(SIGCHLD,sigchild) == SIG_ERR)
{
perror("signal");
return -1;
}
//创建侦听套接字
printf("服务器:创建套接字\n");
int sockfd = socket(AF_INET,SOCK_STREAM,0);//返回侦听套接字
if(sockfd == -1)
{
perror("socket");
return -1;
}
printf("sockfd:%d\n",sockfd);
//组织地址结构,代表服务器
printf("服务器:组织地址结构\n");
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(8888);//注意大小端问题,存是小端,服务器大端方式拿。
//ser.sin_addr.s_addr = inet_addr("127.0.0.1")
ser.sin_addr.s_addr = INADDR_ANY;//表示接受任意IP下的地址
//绑定套接字和地址结构
printf("服务器:绑定套接字和地址结构\n");
int sockbind = bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
if(sockbind == -1)
{
perror("bind");
return -1;
}
//开启侦听功能
printf("服务器:开启侦听功能\n");
int socklisten = listen(sockfd,1024);
if(socklisten == -1)
{
perror("listen");
return -1;
}
//等待并接收客户端连接
for(;;)
{ //父进程负责和客户端建立通信
printf("服务器:等待并接收客户端连接\n");
struct sockaddr_in cli;//用来输出客户端的地址结构
socklen_t len = sizeof(cli);
int conn = accept(sockfd,(struct sockaddr*)&cli,&len);//返回后续用来通信的通信套接字
if(conn == -1)
{
perror("accept");
return -1;
}
printf("服务器:接收到%s:%hu的客户端\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
//子进程负责和客户端通信
pid_t pid = fork();
if(pid == -1)
{
perror("fork");
return -1;
}
if(pid == 0)
{
close(sockfd);//子进程不用侦听套接字,关闭
//业务处里(接发数据)
printf("服务器:接发数据\n");
//接收客户端发来的小写的串
while(1)
{
char buf[128] = {};
ssize_t size = read(conn,buf,sizeof(buf) - sizeof(buf[0]));
if(size == -1)
{
perror("read");
return -1;
}
if(size == 0)
{
printf("服务端:客户端断开连接\n");
break;
}
for(int i = 0; i < size; i++)
{
//转换大写
buf[i] = toupper(buf[i]);
}
//发送给客户端
if(write(conn,buf,size) ==-1)
{
perror("write");
return -1;
}
}
//关闭套接字
printf("服务器:关闭套接字\n");
close(conn);
return 0;
}
close(conn);//父进程关闭通信套接字
}
close(sockfd);
return 0;
}
//基于TCP协议的客户端
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
int main()
{
//创建服务器套接字
printf("客户端:创建套接字\n");
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd ==-1)
{
perror("socket");
return -1;
}
//组织服务器的地址结构
printf("客户端:组织服务器的地址结构\n");
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(8888);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");//不能像服务器宏一样
//发起连接
printf("客户端:发起连接\n");
if(connect(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1 )
{
perror("connect");
return -1;
}
//业务处理
printf("客户端:业务处理\n");
for(;;)
{
char buf[128] = {};
fgets(buf,sizeof(buf),stdin);
//循环退出条件
if(strcmp(buf,"!\n") == 0)
{
break;
}
//将小写传发送给服务器
if(send(sockfd,buf,strlen(buf),0) == -1 )
{
perror("send");
return -1;
}
//接收服务器回传的大写的串
if(recv(sockfd,buf,sizeof(buf) - sizeof(buf[0]),0) == -1)
{
perror("recv");
return -1;
}
printf(">>%s",buf);
}
//关闭套接字
printf("客户端:关闭套接字\n");
close(sockfd);
}
服务器:
服务器:创建套接字
sockfd:3
服务器:组织地址结构
服务器:绑定套接字和地址结构
服务器:开启侦听功能
服务器:等待并接收客户端连接
服务器:接收到127.0.0.1:57272的客户端
服务器:等待并接收客户端连接
服务器:接发数据
服务器:接收到127.0.0.1:57274的客户端
服务器:等待并接收客户端连接
服务器:接发数据
服务器:接收到127.0.0.1:57276的客户端
服务器:等待并接收客户端连接
服务器:接发数据
服务端:客户端断开连接
服务器:关闭套接字
回收13715进程僵尸
子进程在运行,没法收
服务端:客户端断开连接
服务器:关闭套接字
回收13715进程僵尸
子进程在运行,没法收
服务端:客户端断开连接
服务器:关闭套接字
回收13715进程僵尸
没有子进程可回收
客户端:
客户端:创建套接字
客户端:组织服务器的地址结构
客户端:发起连接
客户端:业务处理
fwefw
>>FWEFW
egetg
>>EGETG
!
客户端:关闭套接字
客户端:创建套接字
客户端:组织服务器的地址结构
客户端:发起连接
客户端:业务处理
^[FEFWfw
greg
>>GREG
etgte
>>ETGTE
!
客户端:关闭套接字