(一)网络socket的基础知识
在这里只是简单说说socket基础知识。当然,这里其实还涉及到计算机网络的知识等(例如:TCP/IP协议是怎么回事,client与server是怎么实现传输数据等)。
(1) 什么是网络socket
定义:套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。(来源:百度百科)
从定义中可以看到,完成一个简单的socket编程需要这几个点:I/O文件操作,IP/PORT等。
(2) socke的CS流程图:
我们可以看到,server端主要分为4步走(socket,bind,listen,accept)。
而client分为2步走:(socket,connect)。
(3) socket的基本函数
3.1 socket()函数
socket()函数:socket函数是一种可用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源的函数。(简单来说:就相当于文件打开)
(来源:百度百科)
int socket(int domain, int type, int protocol);
参数 | 功能 |
---|---|
domain(协议域)或称为family(协议族) | 常用协议族:AF_INET(ipv4地址)、AF_INET6(ipv6地址)、AF_LOCAL(绝对路径名作为地址) |
type(类型) | 指定socket类型。常用的socket类型有:SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)、SOCK_RAW |
protoco(协议)l | 指定协议。常用协议:IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP等。(参数为0:默认选择type对应默认类型) |
3.2 bind ()函数
bind ()函数:将一本地地址与一套接口捆绑。当我们调用socket创建套接口后,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。所以,我们通过bind()函数对其进行地址赋值。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数 | 功能 |
---|---|
sockfd(描述字) | sockfd由socket()函数创建的。通过bind()函数给sockfd绑定一个名字 |
const struct sockaddr *addr | addr指向sockaddr结构体类型的指针(指向要绑定的sockfd的协议地址) |
addrlen | 地址的长度 |
3.3 listen()函数
listen()函数:监听这个socket,并将socket变为被动类型,等待客户的连接请求。
int listen(int sockfd, int backlog);
参数 | 功能 |
---|---|
sockfd | socket()系统调用创建的要监听的socket描述字 |
backlog(连接队列。英文翻译是:堆积的工作) | 相应socket可以在内核里排队的最大连接个数 |
深入了解backlog:
https://blog.csdn.net/yangbodong22011/article/details/60399728
backlog包括:半连接状态,全连接状态。
半连接状态:服务器处于Listen状态时收到客户端SYN报文时放入半连接队列中,即SYN queue(服务器端口状态为:SYN_RCVD)。
全连接状态:TCP的连接状态从服务器(SYN+ACK)响应客户端后,到客户端的ACK报文到达服务器之前,则一直保留在半连接状态中。
3.4 accept()函数
accept()函数:调用accpet()接受来自客户端的连接请求,这个函数默认是一个阻塞函数,这也意味着如果没有客户端连接服务器的话该程序将一直阻塞着不会返回,直到有一个客户端连过来为止。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数 | 功能 |
---|---|
sockfd | socket()系统调用创建的要监听的socket描述字 |
*addr | 用于返回client(客户端)的协议地址,这个地址里包含有客户端的IP和端口信息等 |
addrlen | 返回客户端协议地址的长度 |
3.5 connect()函数
connect()函数:客户端调用connect()发出连接请求,服务器端就会接收到这个请求并使accept()返回。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数 | 功能 |
---|---|
sockfd | socket()系统调用创建的要监听的socket描述字 |
*addr | lserver(服务器)的协议地址,这个地址里包含有服务器的IP和端口信息等 |
addrlen | 地址的长度 |
(4) 网络字节序和主机字节序
4.1 主机字节序(大端、小端)
字节序:整数在内存中保存的顺序。
小端字节序:低位字节防在内存的低地址端,高位字节防在内存的高地址端。
大端字节序:高位字节防在内存的低地址端,低位字节防在内存的高地址端。
4.2 网络字节序
网络字节序:4个字节32bit值分四次进行次序传输。(称为:大端字节序(因为:CP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,也称为网络字节序))。
4.3主机字节序转成网络字节序
htons() //s是short :2字节 端口
htonl() //l是long : 4字节 IP
例如:
serv_addr.sin_port = htons(LISTEN_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
(二)CS(client&server)的编程
(1) server端编程(四步)
1.1 socket ()代码
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#define LISTEN_PORT 8889 //tcp ddi-tcp-2 desktop data tcp 1
#define BACKLOG 13
int main(int argc,char **argv)
{
int listen_fd=-1;
int client_fd=-1;
int rv=-1;
struct sockaddr_in cli_addr; //netinet/in.h
struct sockaddr_in serv_addr;
socklen_t cliaddr_len;
char buf[1024];
int listen_fd=-1;
listen_fd=socket(AF_INET,SOCK_STREAM,0); unistd.h
if(listen_fd <0)
{
printf("create socket failure:%s\n",strerror(errno));//stdio.h string.h errrno.h
return -1;
}
else
{
printf("socket create fd[%d]\n",listen_fd);
}
简析:
这里调用socket()获取了相应的fd,再判断fd获取的值是否成功。
1.2 bind ()代码
memset(&serv_addr,0,sizeof(serv_addr)); //memset
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(LISTEN_PORT); //PORT
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(listen_fd,(struct sockaddr *)&serv_addr,sizeof(serv_addr))<0)
{
printf("create socket failure:%s\n",strerror(errno));
return -2;
}
else
{
printf("socket[%d] bind on port[%d] for all IP address ok\n",listen_fd,LISTEN_PORT);
}
简析:
因为要调用bind()函数进行IP和port的绑定,所以要用到struct sockaddr_in serv_add结构体,这个结构体包含了family(因为这里是ipv4 所以AF_INET),IP(涉及到字节序转化htonl),port(涉及到字节序转化htons)。接着就是用if语句进行bind的绑定判断等。
1.3 listen () 和 accept ()代码
listen(listen_fd,BACKLOG);
while(1)
{
printf("\nStart waiting and accept new client connect...\n",listen_fd);
client_fd=accept(listen_fd,(struct sockaddr*)&cli_addr,&cliaddr_len);// server-client
if(client_fd<0)
{
printf("accept new socket failure:%s\n",strerror(errno));
return -2;
}
else
{
printf("accept new socket [%s:%d] with fd[%d]\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port),client_fd); //netinet/in.h 、arpa/inet.h
}
简析:
调用listen()后,socket变为被动,等待客户端连接。用一个while语句去等。
1.4 I/O文件调用代码
memset(buf,0,sizeof(buf));
if((rv=read(client_fd,buf,sizeof(buf)))<0)
{
printf("Read data from cilent socket [%d] failure:%s\n",client_fd,strerror(errno));
close(client_fd);
continue;
}
else if(rv==0)
{
printf("client socket[%d] disconnected\n",client_fd);
close(client_fd);
continue;
}
//read success :back the rv,cilent_fd,buf
else
{
printf("read %d data from client [%d] and echo it back:'%s'\n",rv,client_fd,buf);
}
//write :put buf to client_fd
if((write(client_fd,buf,rv))<0)
{
printf("Write %d bytes date back to client [%d] failure:'%s'\n",rv,strerror(errno));
close(client_fd);
}
sleep(1);
close(client_fd);
}
close(listen_fd);
}
简析:
连接成功后,就是把获取到的client_fd进行操作。
(2)client 端编程
2.1 socket ()代码
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#define SERVER_IP "127.0.0.1" //本机
#define SERVER_PORT 8889
#define MSG_STR "HELLO,linux network program world!"
int main(int argc,char **argv)
{
int conn_fd=-1;
int rv=-1;
char buf[1024];
struct sockaddr_in serv_addr;
//1.socket()
conn_fd=socket(AF_INET,SOCK_STREAM,0);
if(conn_fd<0)
{
printf("create socket failure: %s\n",strerror(errno));
return -1;
}
2.2 connect()代码
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(SERVER_PORT);
inet_aton(SERVER_IP,&serv_addr.sin_addr); //点分十进制 转化 32位整型类型
//2.connect()
if(connect(conn_fd,(struct sockaddr *)&serv_addr,sizeof(serv_addr))<0)
{
printf("connect to server[%s:%d] failure: %s\n",SERVER_IP,SERVER_PORT,strerror(errno));
return 0;
}
简析:
先用memset进行地址初始化。然后,通过已知的服务器的IP,PORT之类的。然后就可以对服务器进行连接了。
2.3 I/O文件调用代码
memset(buf,0,sizeof(buf));
rv=read(conn_fd,buf,sizeof(buf));
if(rv<0)
{
printf("read data from server failure: %s\n",strerror(errno));
goto cleanup;
}
else if(rv==0)
{
printf("client connect to server get disconnected\n");
goto cleanup;
}
else
{
printf("read %d bytes data from sever:%s \n",rv,buf);
}
cleanup:
close(conn_fd);
}
简析:
建立连接后,我们就可以通过conn_fd进行操作了。