Socket套接字
1:Socket
是一个编程接口(
网络编程接口
),是一种特殊的
文件描述符
(
write/read
)。
Socket
并不仅限于 TCP/IP。
2:Socket
独立于具体协议的编程接口,这个接口位于
TCP/IP
四层模型的应用层与传输层
之间
Socket的类型
流式套接字
:(
SOCK_STREAM
)
面向字节流,针对于传输层协议为
TCP
协议的网络应用
数据报套接字
:(
SOCK_DGRAM
)
面向数据报,针对于传输层协议为
UDP
协议的网络应用
原始套接字
:
(
SOCK_RAW
)
直接跳过传输层
基于TCP套件字编程基本要求
1:任何网络应用都会有通信双方:
Send
发送端
recv
接收端
2:TCP 网络应用
Client
客户端(
TCP
)
Server
服务端(
TCP
)
3:任何的网络应用
:
传输层的协议(
TCP/UDP
)
+
端口
+
IP
地址
4:网络地址
:
任何网络应用任意一方都需要有一个
网络地址
(
IP+
端口
)
TCP网络应用执行的大致过程
建立连接 :
三次握手
发送/接收数据:
发送数据:
write/send/sendto
接收数据:
read/recv/recvfrom
关闭连接:
四次挥手
TCP网络应用的编程流程
TCP-Server服务端
建立一个套件字
SOCKET(2) Linux Programmer's Manual
SOCKET(2)
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:
指定域/协议簇。socket接口不仅仅局限于TCP/IP,它可以用于
Bluetooth、本地通信...
每一种下面都有自己的许多协议,我们把IPV4下面的所有协议都归纳到了一
个域:
AF_INET IPV4
AF_INET6 IPV6
AF_UNIX AF_LOCAL 本地通信
AF_BULETOOTH 蓝牙
...
@type:
指定要创建的套件字的类型:
SOCK_STREAM 流式套接字
SOCK_DGRAM 数据报套接字
SOCK_RAW 原始套接字
...
TCP采用流式套接字,UDP采用数据报套接字
@protocol
协议,指定具体的应用层协议,可以指定为0:表示采用不知名的私有的应用
层
@return:
成功返回一个套接字描述符
是吧返回-1,同时errno被设置
*/
绑定一个网络地址
并不是任意的地址都可以(需要合法且能够正常访问)
把一个套接字和一个网络地址进行绑定。如果想让其他人来主动联系
/
连接,就需要绑定一个
地址,并且需要把这个地址告诉其他人。不进行绑定,并代表套接字没有地址,不进行绑定套
接字在进行通信时候,内核会动态为套接字指定一个地址
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t
addrlen);
/*
@描述:
用于给一个指定的套接字绑定网络地址
@sockfd:
需要绑定地址的套接字
@addr:
一个结构体类型,表示网络地址
socket接口不仅可以用于以太网(IPV4),也可以用于IPV6,同时也
可以用于Bluetooth,....
不同的协议簇,它的地址是不一样的。
socket编程接口,用一个通用的 “ 网络地址接口 ”
struct sockaddr
{
sa_family_t sin_family; // 指定协议簇
char sa_data[14];
};
协议地址结构:
struct sockaddr_in
{
sa_family_t sin_family; // 指定协议簇
u_int16_t sin_port; // 端口号
struct in_addr sin_addr;// IP地址
char sin_zero[8]; // 填充8字节,为了和
其他协议簇地址结构体大小一样
};
如:
struct sockaddr_in sock_info;
sock_info.sin_family = AF_INET; // 指定为IPV4
sock_info.sin_port = htons(6666); //指定为
6666端口
sock_info.sin_addr.s_addr =
inet_addr("192.168.31.1"); // 绑定ip地址
//
inet_aton("192.168.31.1",&sock_info.sin_addr);
bind(sock,(struct sockaddr
*)&sock_info,sizeof(sock_info));
@addrlen
表示网络地址结构体的大小
@return:
成功返回0,失败返回-1
*/
等待监听
LISTEN(2) Linux Programmer's Manual
LISTEN(2)
NAME
listen - listen for connections on a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
/*
@描述
设置指定的套接字进入监听模式
@sockfd:
需要进入监听模式的套接字
@backlog:
可以处理的最大请求数目,可以理解为发起请求的客户端的队伍可以有多长
@return:
成功返回0,失败返回-1
*/
等待客户端的连接
等待客户端来发起连接和客户端建立
TCP
连接 :
三次握手
多次调用函数就可以与不同的客户端进行连接
ACCEPT(2) Linux Programmer's Manual
ACCEPT(2)
NAME
accept, accept4 - accept a connection on a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t
*addrlen);
/*
@描述:
等待客户端连接套接字,等待客户端发起连接请求
@sockfd:
等待客户端连接的那个套接字
@addr:
网络地址结构体,用于存储连接成功的客户端信息的。
@addrlen:
网络地址结构体的长度指针,用来保存客户端地址结构体的长度的。
在调用的时候addrlen指向的空间保存的是addr的结构体的最大长度。
如果函数成功返回,addrlen指向的空间保存的是client客户端地址的结构
体长度。
@return:
成功返回与该客户端的连接套接字的描述符(后续服务端和客户端的数据通
信,通过该套件字通信 )
失败返回-1,同时errno被设置。
*/
数据的传输
发送数据:
write/send/sendto
接收数据:
read/recv/recvfrom
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
/*
作用:
往指定套接字中写入数据
@sockfd:
需要写入数据的套接字描述符
@buf:
需要写入的数据空间的指针
@len:
数据的长度
@flags:
一般给0,” 带外数据 “
@return:
成功返回实际发送的字节数,失败返回-1,同时...
*/
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
/*
作用:
从指定套接字中获取数据
@sockfd:
需要读取数据的套接字描述符
@buf:
读取到的数据所要保存的空间的指针
@len:
需要获取的数据的长度
@flags:
一般给0,” 带外数据 “
@return:
成功返回实际获取的字节数,失败返回-1,同时...
*/
关闭套接字
#include <sys/socket.h>
int shutdown(int sockfd, int how);
/*
作用:
关闭一个套接字
@sockfd:
需要关闭操作的套接字描述符
@how:
关闭方式:
SHUT_RD 关闭读
SHUT_WR 关闭写
TCP-Client客户端
建立一个套接字: socket
有Server服务端一样
绑定地址
可选/可以绑定也可也不绑定
发起连接请求
主动的与
TCP
-
Server
建立连接。三次握手
CONNECT(2) Linux Programmer's Manual
CONNECT(2)
NAME
connect - initiate a connection on a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t
addrlen);
/*
@描述:
用指定的套接字,对指定网络地址发起连接请求
@sockfd:
发起连接请求的套接字
同时这个套接字是与服务端进行数据通信的套接字
@addr:
需要连接到的网络地址,目标地址
@addrlen:
目标地址结构体的大小
@return:
成功返回0,失败返回-1
*/
数据的传输: 读/写
发送数据:
write/send/sendto
接收数据:
read/recv/recvfrom
关闭套接字: close
举一个代码示例:
Server服务端
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
typedef enum
{
CMD,
MSG,
IMG
}Data_t;
struct package
{
// 数据类型
Data_t DataType;
// 数据实际的大小
int DataSize;
// 数据本体
unsigned char Datas[0]; // 柔性数组
};
/*
通过main函数的参数传递IP和端口
*/
int main(int argc,const char *argv[])
{
// 申请套接字
int sock_fd = socket(AF_INET,SOCK_STREAM,0);
if(sock_fd == -1)
{
perror("申请失败");
return -1;
}
std::cout << "套接字申请成功" << std::endl;
// 绑定网络地址
struct sockaddr_in local; // 绑定本机的网络地址
local.sin_family = AF_INET;
local.sin_addr.s_addr = inet_addr(argv[1]); // 将字符串的ip地址转化为网络字节序列
local.sin_port = atoi(argv[2]);
if(bind(sock_fd,(struct sockaddr*)&local,sizeof(local)) == -1)
{
perror("绑定失败");
close(sock_fd);
return -1;
}
std::cout << "套接字绑定成功" << std::endl;
// 进入监听状态
if(listen(sock_fd,10) == -1)
{
perror("监听失败");
close(sock_fd);
return -1;
}
std::cout << "套接字监听成功" << std::endl;
std::cout << "等待客户端连接..." << std::endl;
// 等待客户端连接
struct sockaddr_in client;
socklen_t client_size = sizeof(client);
int newSock = accept(sock_fd,(struct sockaddr*)&client,&client_size);
// 判断客户端是否连接成功
if(newSock == -1)
{
perror("客户端连接失败");
close(sock_fd);
return -1;
}
std::cout << "客户端连接成功" << std::endl;
// 数据传输
while(1)
{
struct package *data = (struct package*)malloc(sizeof(struct package));
// 获取第一次数据
int ret = read(newSock,data,sizeof(struct package));
if(ret == -1)
break;
std::cout << "本次数据包的大小:" << data->DataSize << std::endl;
// 重新为结构体分配空间
data = (struct package*)realloc(data,sizeof(struct package)+data->DataSize);
// 获取第二次数据
ret = read(newSock,data->Datas,data->DataSize);
if(ret == -1 || std::string((char *)data->Datas) == "exit")
break;
std::cout << "来自客户端的消息<" << inet_ntoa(client.sin_addr) << ">" << data->Datas << std::endl;
free(data);
}
// 关闭套接字
close(sock_fd);
return 0;
}
Client客户端
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
typedef enum
{
CMD,
MSG,
IMG
}Data_t;
struct package
{
// 数据类型
Data_t DataType;
// 数据实际的大小
int DataSize;
// 数据本体
unsigned char Datas[0]; // 柔性数组
};
/*
通过main函数的参数传递IP和端口
*/
int main(int argc,const char *argv[])
{
// 申请套接字
int sock_fd = socket(AF_INET,SOCK_STREAM,0);
if(sock_fd == -1)
{
perror("申请失败");
return -1;
}
std::cout << "套接字申请成功" << std::endl;
std::cout << "客户端发起连接..." << std::endl;
// 等待客户端连接
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(argv[1]);
server.sin_port = atoi(argv[2]);
if(connect(sock_fd,(struct sockaddr*)&server,sizeof(server) )== -1)
{
perror("连接失败");
close(sock_fd);
return -1;
}
std::cout << "客户端连接成功" << std::endl;
// 数据传输
while(1)
{
std::string str;
std::cout << "输入消息:";
std::cin >> str;
// 应用层封包过程
struct package *data = (struct package*)new char[sizeof(struct package)+str.size()];
data->DataType = MSG;
data->DataSize = str.size();
strcpy((char *)data->Datas,str.c_str());
int ret = write(sock_fd,data,sizeof(struct package)+str.size());
if(ret == -1||str == "exit")
break;
}
// 关闭套接字
close(sock_fd);
return 0;
}