概述
什么是套接字
套接字是一个主机本地应用程序所创建的,为操作系统所控制的接口。
应用进程通过这个接口,使用传输层提供的服务,跨网络发送/接收消息到其他应用进程。
Client/Server模式的通信接口——套接字接口
套接字:描述符
OS将文件描述符实现为一个指针数组,指向一个内部的数据结构:进程描述符表的下标
套接字和文件类似,每个活动套接字使用一个小整数标识,进程的文件描述符和套接字描述符值不能相同
socket函数:创建套接字描述符(不是open函数)
进程的文件描述符表
socket
int socket(int domain,int type,int protocol)
功能:创建一个新的套接字,返回套接字描述符
参数说明:
domain:域类型,指明使用的协议栈,如TCP/IP使用的是PF_INET
type:指明需要的服务类型,如
SOCK_DGRAM:数据报服务,UDP协议
SOCK_STREAM:流服务,TCP协议
protocol:一般都取0
举例:s = socket(PF_INET,SOCK_STREAM,0)
connect
int connect(int sockfd,struct sockaddr *server_addr,int sockaddr_len)
功能:同远程服务器建立主动连接,成功时返回0,若连接失败返回-1
参数说明:
Sockfd:套接字描述符,指明创建连接的套接字
Server_addr:指明远程端点:IP地址和端口号
sockaddr_len:地址长度
举例(P49):connect(s,remaddr,remaddrlen)
send
int send(int sockfd,const void * data,int data_len,unsigned int flags)
功能:
在TCP连接上发送数据,返回成功传送数据的长度,出错时返回-1
send会将外发数据复制到OS内核中,也可以使用send发送面向连接的UDP报文
参数说明:
sockfd:套接字描述符
data:指向要发送数据的指针
data_len:数据长度
flags:一直为0
recv
int recv(int sockfd,void *buf,int buf_len,unsigned int flags);
功能:
从TCP接收数据,返回实际接收的数据长度,出错时返回-1
服务器使用其接收客户请求,客户使用它接受服务器的应答。如果没有数据,将阻塞,如果收到的数据大于缓存的大小,多余的数据将丢弃。也可以使用recv接收面向连接的UDP的报文
参数说明:
Sockfd:套接字描述符
Buf:指向内存块的指针
Buf_len:内存块大小,以字节为单位
flags:一般为0
举例:recv(sockfd,buf,8192,0)
close
close(int sockfd);
功能:
撤销套接字
如果只有一个进程使用,立即终止连接并撤销套接字,如果多个进程共享该套接字,将引用数减一,如果引用数降到零,则撤销它。
参数说明:
Sockfd:套接字描述符
举例:close(socket_descriptor)
bin
int bin(int sockfd,struct sockaddr *my_addr,int addrlen)
功能:为套接字指明一个本地端点地址
TCP/IP协议使用sockaddr_in结构,包含IP地址和端口号
服务器使用它来指明熟知的端口号,然后等待连接
参数说明:
Sockfd:套接字描述符,指明创建连接的套接字
my_addr:本地地址,IP地址和端口号
addrlen:地址长度
举例:bin(sockfd,(struct sockaddr*)&address,sizeof(address));
listen
int listen(int sockfd,int input_queue_size)
功能:
面向连接的服务器使用它将一个套接字置为被动模式,并准备接收传入连接。用于服务器,指明某个套接字连接是被动的。
参数说明:
Sockfd:套接字描述符,指明创建连接的套接字
input_queue_size:该套接字使用的队列长度,指定在请求队列中允许的最大请求数
举例:listen(sockfd,20)
accept
int accept(int sockfd,void *addr,int *addrlen);
int accept(int sockfd,struct sockaddr *addr,int *addrlen);
功能:获取传入连接请求,返回新的连接的套接字描述符。
为每个新的连接请求创建了一个新的套接字,服务器只对新的连接使用该套接字,原来的监听套接字接受其他的连接请求。
新的连接上传输数据使用新的套接字,使用完毕,服务器将关闭这个套接字。
参数说明:
Sockfd:套接字描述符,指明正在监听的套接字
addr:提出连接请求的主机地址
addrlen:地址长度
举例:
new_sockfd = accept(sockfd,(struct sockaddr*)&address,sizeof(address));
用于整数转换的实用例程
网络字节顺序:最高位字节在前
有结套接字例程要求参数按照网络字节顺序存储。如sockaddr_in
需要网络字节顺序和本地主机字节顺序进行转换的函数,坚持使用,便于移植。
分为短(short 16位)和长(long 32位)两种
htons:将一个短整数从本地字节顺序转换为网络字节顺序;
ntohs:将一个短整数从网络字节顺序转换为本地字节顺序;
htonl和ntohl:类似如上
套接字API中的主要系统调用
read和write
在UNIX和Linux中,可以代替recv和send,因为都调用内核的sosend实现。
Linux
TCP服务端
Ubuntu 16.04.6 LTS
mkdir tcpserver
cd tcpserver
vim tcpserver.cpp
#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
using namespace std;
//#ifndef errno
//extern int errno;
//#endif
int main(int argc,char *argv[])
{
unsigned short port = 8080;
if(argc>1)
{
port = atoi(argv[1]);
}
//1 create socket
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock<=0)
{
cerr<<"cretor socket error "<<strerror(errno)<<endl;
return -1;
}
//2 bind port
sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);
saddr.sin_addr.s_addr = htonl(0);
int re = ::bind(sock,(sockaddr*)&saddr,sizeof(saddr));
if(re != 0)
{
cerr<<"bind port "<<port<<" failed!"<<strerror(errno)<<endl;
return -1;
}
cout<<"bind port "<<port<<" success!"<<endl;
//3 listen
listen(sock,10);
//4 accept
{
sockaddr_in caddr;
socklen_t addrlen = 0;
int client_sock = accept(sock,(sockaddr*)&caddr,&addrlen);
cout<<"client sock = "<<client_sock<<endl;
// send
char buf[1024] = "wellcome to xms";
int len = send(client_sock,buf,strlen(buf),0);
cout<<"send len = "<<len<<endl;
// recv
len = recv(client_sock,buf,sizeof(buf)-1,0);
if(len>0)
{
buf[len] = '