目录
socket介绍
socket可以看作是网络中不同主机上的应用进程之间进行双向通信的端口的抽象,是应用程序通过网络协议进行通信的接口。
Linux环境下,socket用于表示进程间通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。可以用文件描述符来引用套接字。
TCP通信流程
服务端通信流程
1.创建一个用于监听的套接字
-监听:监听客户端的连接
-套接字:这个套接字其实就是一个文件描述符
2.将这个监听文件描述符和本地的ip和端口绑定(ip和端口就是服务器的地址信息)
-客户端连接服务器的时候使用的就是这个ip和端口
3.设置监听,监听的fd开始工作
4.阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个和客户端通信的套接字(fd)
5.通信
-接收数据
-发送数据
6.通信结束,断开连接
客户端通信流程
1.创建一个用于通信的套接字(fd)
2.连接服务器,需要指定连接的服务器的IP和端口
3.连接成功了,客户端可以直接和服务器通信
-接收数据
-发送数据
4.通信结束,断开连接
函数原型
头文件
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略
1.socket
int socket(int domain, int type, int protocol);
功能:创建一个套接字,指定协议类型
参数:
-domain:协议族
AF_INET:ipv4
AF_INET6:ipv6
AF_UNIX,AF_LOCAL:本地套接字通信(进程间通信)
-type:通信过程中使用的协议类型
SOCK_STREAM:流式协议 默认tcp
SOCK_DGRAM:报式协议 默认udp
-protocol:默认0
2.bind
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:绑定,将fd和本地ip+端口进行绑定
参数:
sockfd:通过socket函数得到的文件描述符
addr:需要绑定的socket地址,这个地址封装了ip和端口号信息
addrlen:第二个参数的大小
关于这个addr,TCP/IP 协议族有 sockaddr_in 和 sockaddr_in6 两个专用的 socket 地址结构体,它们分别用于IPv4和IPv6这里采取sockaddr_in进行详细分析,所有专用 socket 地址(以及 sockaddr_storage)类型的变量在实际使用时都需要转化为通用 socket 地 址类型 sockaddr(强制转化即可),因为所有 socket 编程接口使用的地址参数类型都是 sockaddr。
struct sockaddr_in
{
sa_family_t sin_family; /* __SOCKADDR_COMMON(sin_) */
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) - sizeof (struct in_addr)];
};
3.listen
int listen(int sockfd, int backlog);
功能:监听这个socket上的连接
参数
sockfd:通过socket()函数的到文件描述符
backlog:未连接和已连接的和的最大值,为建立好连接处于ESTABLISHED状态的队列的长度,有多余这个数的连接,是SYN_RECV状态,最多只有backlog个ESTABLISHED状态。
4.accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:接受客户端请求,默认阻塞等待客户端连接
参数:
sockfd:用于监听的文件描述符
addr:传出参数,记录了连接成功后的客户端ip和端口号
addrlen:第二个参数对应的内存大小
5.connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:客户端连接服务器,阻塞
参数:
sockfd:用于通信的文件描述符
addr:客户端要连接的服务器的地址信息
addrlen:第二个参数的内存大小
返回值:成功0,失败-1
6.读写数据
ssize_t write(int fd, const void *buf, size_t count); // 写数据
ssize_t read(int fd, void *buf, size_t count); // 读数据
案例
服务器:
#include<arpa/inet.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h> // read write close
#include<stdlib.h>
int main()
{
//1.创建socket套接字
int lfd=socket(AF_INET,SOCK_STREAM,0);
if(lfd==-1){
perror("socket");
exit(-1);
}
printf("1.创建socket成功\n");
//2.绑定套接字端口号
struct sockaddr_in saddr;
saddr.sin_family=AF_INET;//ipv4
saddr.sin_addr.s_addr=INADDR_ANY;//接受任意ip地址
saddr.sin_port=htons(9999);//端口9999 主机字节序转换成网络字节序
int ret = bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(ret == -1){
perror("ret");
exit(-1);
}
printf("2.服务端绑定套接字成功\n");
//3.监听
ret=listen(lfd,8);
if(ret==-1){
perror("listen");
exit(-1);
}
printf("3.服务端监听套接字成功\n");
//4.接受客户端连接
struct sockaddr_in caddr;
int len=sizeof(caddr);
int cfd = accept(lfd,(struct sockaddr*)&caddr,&len);
if(cfd==-1){
perror("accept");
exit(-1);
}
printf("4.服务端成功接受客户端连接请求\n");
//5.通信
char recv_buf[1024]={0};
while(1)
{
memset(recv_buf,0,sizeof(recv_buf));
int data=read(cfd,recv_buf,sizeof(recv_buf));
if(data<0){
perror("read");
exit(0);
}
else if(data==0){
printf("客户端断开连接....\n");
close(lfd);
close(cfd);
exit(0);
}
printf("收到客户端数据:%s\n",recv_buf);
printf("请输入发送给客户端的数据");
memset(recv_buf,0,sizeof(recv_buf));
scanf("%s",recv_buf);
data=write(cfd,recv_buf,strlen(recv_buf)+1);
if(data<0){
perror("write");
exit(0);
}
printf("服务端发送数据成功\n");
}
close(cfd);
close(lfd);
return 0;
}
客户端:
#include<arpa/inet.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h> // read write close
#include<stdlib.h>
int main()
{
//1.创建套接字
int cfd=socket(AF_INET,SOCK_STREAM,0);
if(cfd==-1){
perror("socket");
exit(0);
}
printf("1.客户端创建套接字成功\n");
//2.连接服务器
struct sockaddr_in saddr;
saddr.sin_family=AF_INET;
saddr.sin_port=htons(9999);
inet_pton(AF_INET,"192.168.108.128",&saddr.sin_addr);//服务器的ip地址
/*
p:点分十进制的IP字符串,n:表示network,网络字节序的整数
int inet_pton(int af, const char *src, void *dst);
af:地址族: AF_INET AF_INET6
src:需要转换的点分十进制的IP字符串
dst:转换后的结果保存在这个里面
*/
int ret=connect(cfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(ret==-1){
perror("connect");
exit(-1);
}
printf("2.客户端服务器连接成功\n");
//3.通信
char recvBuf[1024]={0};
while(1){
printf("请输入你想要发给客户端的内容\n");
memset(recvBuf,0,sizeof(recvBuf));
scanf("%s",recvBuf);
write(cfd,recvBuf,strlen(recvBuf)+1);
memset(recvBuf,0,sizeof(recvBuf));
int data=read(cfd,recvBuf,sizeof(recvBuf));
if(data>0)
{
printf("服务端返回数据:%s\n",recvBuf);
}
else if(data==0){
printf("服务端断开连接\n");
}else if(data<0){
perror("read");
exit(-1);
}
}
close(cfd);
return 0;
}