所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制
流式套接字基本概念
创建套接字
int socket(int domain,int type,int protocol);
返回值:文件描述符表示成功,-1表示错误,errno记录错误代码
-
套接字的域名domain
代表套接字的地址族域名 地址族 AF_UNIX,AF_LOCAL 用于本地通信 AP_INET,PF_INET IPv4,Internet协议 AF_INET6 IPv6,Internet协议 AF_IPX Novell网络协议 -
套接字的类型type
最常用的两个值是
SOCK_STREAM
和SOCK_DGRAM
-
SOCK_STREAM流式套接字
流式套接字不保留任何消息的边界,他只是简单的将它所有的数据交给接收方。
此外,数据严格按照写入时的顺序被接收端所读取。在IP协议中,分组可以按照不同的路由被转发到目的地,因此在数据接受端会发生分组失序现象,而流式套接字可以保证接收端按照数据发送时的顺序正确接收到数据。如果传输时发生错误,则流式传输机制自动进行差错校正。
流式套接字提供的是可靠地数据连接,采用面向连接的方式。
-
SOCK_DGRAM数据报套接字
用于无连接通信,即通信前双方不需要建立任何连接,只要创建了一个非连接的数据报套接字。
UDP协议就是典型的数据报通信方式。
数据报套接字不知道传输过程中是否发生了数据丢失>,因此对于数据差错的发现和纠正只能通过应用层来保证。
-
-
使用的协议protocol
一般情况为0,代表由系统在当前设定的domain下,自动选择适合的协议类型。
绑定本地地址
创建套接字后,该套接字处于未和任何协议地址关联的状态。
如果位于两个不同主机的套接字需要连接而又无地址,那他们无法通信。因此使用bind()将套接字绑定到指定的协议族。
int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
返回值:0表示成功,-1表示失败,errno记录错误代码
-
sockfd
使用socket()函数返回的结果
-
myaddr
分配给套接字的地址指针.
struct sockaddr{//通用套接字地址 sa_family_t sa_family;//地址族 char sa_data[4];//地址数据 } 不会直接使用该结构进行地址设置,他只是作为一个通用类型,以确定所有其他具体地址的结构、
IPv4套接字地址 struct sockaddr_in{ sa_family_t sin_family;//地址族 uint16_t sin_port;//端口 struct in_addr sin_addr;//ip地址 unsigned char sin_zero[8];//占位字节 } struct in_addr{ uint32_t s_addr;//IP地址 }
-
addrlen
myaddr地址的长度
注意,服务器端需要绑定,而客户端不需要。因为通常都是客户端主动发起的连接请求,因此客户端套接字在发出连接请求后,由内核自动绑定到一个临时端口和地址上。而作为服务器,一般是工作在被动连接的方式下,所以必须通过显示的调用bind(),将监听套接字绑定到一个众所周知的端口上,以等待客户端的连接。
连接请求
int connect(int sockfd,const struct sockaddr *servaddr,socklen_t addrlen);
返回值:0表示成功。-1表示失败,errno记录错误代码
用于TCP客户端和TCP服务器建立连接。
-
sockfd
调用socket()生成的套接字
-
servaddr
客户端准备连接的服务器地址
-
addrlen
服务器地址长度
监听函数
int listen(int sockfd,int backlog);
返回值:0成功,-1失败。errno记录错误代码
用于TCP服务器启动监听
-
sockfd
用于监听的套接字
-
backlog
连接队列的长度。是指完成TCP三次握手之后已经成功建立TCP连接的队列长度。服务器执行accept()操作时,从该队列中取下一个连接进行后续处理。默认是128
接收请求
int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);
返回值:文件描述符表示成功,-1表示失败merrno记录错误代码
-
sockfd
用于监听的套接字
-
cliaddr
用于接收客户端套接字地址的地址结构指针。
-
addrlen
指向接收套接字地址缓存最大长度的指针。
如果函数调用成功,他的返回值是一个新的套接字描述符,称为连接套接字,服务器使用该套接字和已经建立连接的客户端进行通信。而原有的监听套接字继续接收后续新客户端发来的连接请求。连接套接字在通信完毕后关闭,但监听套接字会一直监听直到整个应用结束。
套接字IO操作
ssize_t read(int sockfd,void *buf, size_t count);
返回:非0 是所读字节数。0文件尾,-1失败, errno记录错误代码
-
sockfd
用于读操作的套接字
-
buf
用于存放读入数据的缓存
-
count
代表本次read可以接收的最大可读数据字节长度,通常是buf指向缓存的大小。
ssize_t write(int sockfd,const void *buf,size_t count);
返回值:非0表示所写字节数,0表示未写任何数据,-1表示失败,errno记录错误代码、
- sockfd:用于写操作的套接字
- buf:存放被写数据的缓存
- count:被写数据字节的长度,通常为buf指向的输出缓存的大小。
关闭套接字
int close(int sockfd);
int shutdown(int sockfd,int how);
0表示成功.-1失败
前者完全关闭,后者用于希望在完全关闭本地套接字前仍然可以从远端套接字继续接收数据,但不允许本地发送。通常用于即将结束通信的善后处理。
参数how
值 | 宏 | 说明 |
---|---|---|
0 | SHUT_RD | 不允许本地socket进行读操作 |
1 | SHUT_WR | 不允许本地socket进行写操作 |
2 | SHUT_RDWR | 等于close |
编程实现
wins下,cygwin
服务器端
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<netdb.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#define BUFSIZE 512
int main(int argc,char *argv[]){
int sockfd;//服务器套接字
int new_sockfd;//连接套集字
struct sockaddr_in server_addr;//服务器地址 sockaddr_in ipV4
struct sockaddr_in client_addr;//客户端地址
socklen_t size;
int portnumber;//端口号
char reqBuf[BUFSIZE];//接收缓存区
int z;//read()返回值
if(argc!=2){
fprintf(stderr, " %s portnumber\n",argv[0] );
exit(1);
}
if((portnumber=atoi(argv[1]))<0){
fprintf(stderr, "%s portnumber\n",argv[0]);
exit(1);
}
if((sockfd=socket(PF_INET,SOCK_STREAM,0))==-1){
fprintf(stderr, "socket errot:%s\n",strerror(errno));
exit(1);
}
memset(&server_addr,0,sizeof server_addr);
server_addr.sin_family=AF_INET;//IP V4 协议族
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);//host ip->net ,long type
server_addr.sin_port=htons(portnumber);// 端口
if((bind(sockfd,(struct sockaddr *)(&server_addr),sizeof server_addr))==-1){//绑定套接字到指定地址和端口
fprintf(stderr, " bind error: %s\n",strerror(errno));
exit(1);
}
if(listen(sockfd,128)==-1){//监听请求,将请求放入BackLog中,队列
fprintf(stderr, " listen error:%s\n",strerror(errno));
exit(1);
}
printf("wait for the request from client\n");
while(1){
size=sizeof(struct sockaddr_in);
if((new_sockfd=accept(sockfd,(struct sockaddr *)(&client_addr),&size))==-1){
fprintf(stderr, "accept error:%s\n", strerror(errno));
}
fprintf(stdout, "server got connection from %s\n", inet_ntoa(client_addr.sin_addr));
while(1){//接收数据
z=read(new_sockfd,reqBuf,sizeof reqBuf);
if(z<0){
fprintf(stderr, "read error:%s\n",strerror(errno));
exit(1);
}
if(z==0){
printf("colse new_sockfd\n");
close(new_sockfd);
break;
}
reqBuf[z]=0;
printf("receive form client: %s\n",reqBuf);
printf("write down your message to client\n");
fgets(reqBuf,sizeof reqBuf,stdin);
z=write(new_sockfd,reqBuf,sizeof reqBuf);
if(z<0){
printf("write error");
exit(1);
}
}
}
}
客户端
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<netdb.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#define BUFSIZE 512
int main(int argc,char *argv[]){
int sockfd;
char buf[BUFSIZE];
struct sockaddr_in server_addr;
struct hostent *host;
int portnumber;
int nbytes;
int z;
char reqBuf[BUFSIZE];
host=gethostbyname(argv[1]);
portnumber=atoi(argv[2]);
if((sockfd=socket(PF_INET,SOCK_STREAM,0))==-1){
fprintf(stderr, "socket error:%s\n", strerror(errno));
exit(1);
}
memset(&server_addr,0,sizeof server_addr);
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(portnumber);
server_addr.sin_addr=*((struct in_addr*)host->h_addr);
if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof server_addr)==-1){
fprintf(stderr, "connect error:%s\n", strerror(errno));
exit(1);
}
printf("connect success!\n");
while(1){
printf("please input your message to server\n");
if(!fgets(reqBuf,sizeof reqBuf,stdin)){
printf("fgets error!\n");
break;
}
z=strlen(reqBuf);
if(z>0&&reqBuf[--z]=='\n')//末尾添加NULL,同时去掉\n
reqBuf[z]=0;
if(z==0) continue;
//input QUIT to exit
if(!strcasecmp(reqBuf,"QUIT")){
printf("the client colsed.please enter any key to exit\n");
getchar();
write(sockfd,reqBuf,strlen(reqBuf));
break;
}
z=write(sockfd,reqBuf,strlen(reqBuf));
if(z<0){
fprintf(stderr, "write error:%s\n",strerror(errno));
exit(1);
}
if((z=read(sockfd,reqBuf,sizeof(reqBuf)))==-1){
fprintf(stderr, "read error:%s\n",strerror(errno));
exit(1);
}
if(z==0){
printf("the server colsed.please enter any key to exit\n");
getchar();
break;
}
reqBuf[z]=0;
printf("reveice message:%s\n",reqBuf);
}
close(sockfd);
return 0;
}
编译之后,先运行服务器端,然后再打开客户端。通信端口是9000.
左边服务器端,右边客户端(写自己本地IPv4地址,打开CMD,输入IPCONFIG可以看到)
客户端输入quit退出。然后服务器listen到新的连接时,之前accept产生的套接字关闭了。开始新的连接。
参考于:《网络编程与分层协议设计-刘飚》