socket服务器和客户端的开发步骤
TCP服务端:
- 创建套接字socket
- 为套接字添加信息(IP地址和端口号)bind
- 监听网络连接listen
- 监听到由客户端接入,接受一个连接accept
- 数据交互read、write
- 关闭套接字,断开连接close
TCP客户端:
- 创建套接字socket
- 知道IP地址端口号与服务端连接connect
- 数据交互read、write
- 关闭套接字,断开连接close
创建套接字socket
函数原型:
int socket(int domain,int type,int protocol)
参数:
1.domain:指明所使用的协议,通常为AF_INEF,表示互联网协议族(TCP/IP协议族)
- AF_INET --- IPv4因特网域
- AF_INET6 --- IPv6因特网域
- AF_UNIX --- Unix域
- AF_ROUTE --- 路由套接字
- AF_KEY --- 密钥套接字
- AF_UNSPEC --- 未指定
2.type:指定socket的类型
- SOCK_STREAM:流式套接字,面向连接的通信流,它使用TCP协议,从而保证了数据传输的正确性和顺序性。
- SOCK_DGRAM:数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据协议UDP。
- SOCK_RAM:允许程序使用低层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。
3.protocol:通常赋值为0
- 0选择type类型对应的默认协议
- IPPROTO_TCP --- TCP传输协议
- IPPROTO_UDP --- UDP传输协议
- IPPROTO_SCTP --- SCTP传输协议
- IPPROTO_TIPC --- TIPC传输协议
返回值:
成功返回socket套接字描述符,失败返回-1
套接字添加信息bind
用于绑定IP地址和端口号到socketfd
函数原型:
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数:
- int sockfd:socket描述符
- const struct sockaddr *addr:是一个执行包含有本机IP地址及端口号等信息的socket类型的指针,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族的不同而不同。
- socklen_t addrlen:第二个参数结构体的长度
man手册给的第二个参数是:
但是一般写成如下形式,再强转为struct sockaddr *形式
例如如下写法:
监听网络连接listen
listen()的功能:
- 设置能处理的最大连接数,listen()并未开始接受连线,只是设置socket的listen模式,listen函数只用于服务器端,服务器进程不知道要与谁连接,因此它不会主动的要求与某个进程连接,只是一直监听是否有其他客户进程与之链接,然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。主要就两个功能:将一个未连接的套接字转换未一个被动套接字(监听),规定内核为套接字排队的最大连接数。
- 内核为任何一个给监听套接字维护两个队列
- 未完成连接队列,每个这样的SYN报文段对应其中一项:已由某个客户端发出并到达服务器,而服务器正在等待完成响应的TCP三次握手过程,这些套接字处于SYN_REVD状态。
- 已完成连接队列。每个已完成TCP三次握手过程的客户端对应其中一项,这些套接字处于ESTABLISHED状态。
函数原型:
int listen(int sockfd, int backlog);
参数:
- int sockfd:是socket系统调用返回的服务器端socket描述符
- int backlog:指定在请求队列中允许的最大请求数
返回值:
成功返回0,失败返回-1,并且errno中包含相应的错误码
客户端连接服务器connect
功能:
用于绑定之后的client端,与服务器建立连接
函数原型:
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数:
- int sockfd:socket系统调用返回的服务器端socket描述符
- struct sockaddr *addr:用来返回已连接的对端(客户端)的协议地址
- socklen_t *addrlen:客户端地址的长度
返回
成功返回0,失败返回-1,并且errno中包含相应的错误码
接受连接accept
功能:
accept函数由TCP服务器调用,用于从已经完成连接队列队头返回下一个已完成连接,如果已完成连接队列为空,那么进程之间进入睡眠。
函数原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
- int sockfd:socket系统调用返回的服务器端socket描述符
- struct sockaddr *addr:用来返回已连接的对端(客户端)的协议地址
- socklen_t *addrlen:客户端地址的长度
返回值:
该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述都,一个服务器通常仅仅创建一个监听套接字,它在服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户端连接创建一个已连接套接字(表示TCP三次握手协议已完成),当服务器完成对某个给定客户的服务时,响应的已连接套接字就会被关闭。
数据交互read、write
与文件编程中使用的read、write是一模一样的,不过多赘述
此外还要另外两种数据收发API:
- ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- ssize_t recv(int sockfd, void *buf, size_t len, int flags);
其实和read、write也是差不多的,只不过多了一个flag参数,flag表示控制选项,一般设置为0。
字节序转换API
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);返回网络字节序的值
uint16_t htons(uint16_t hostshort);返回网络字节序的值
uint32_t ntohl(uint32_t netlong);返回主机字节序的值
uint16_t ntohs(uint16_t netshort);返回主机字节序的值uint32_t
h代表host,n代表net,s代表short(两个字节),l代表long(四个字节),通过上面4个函数可以表示主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY , INADDR_ANY指定地址让操作系统自己获取。
地址转换API
一般用下面黑体标出的两个
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);把字符串形式的192.168.1.123转为网络能识别的格式
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in);把网络格式的ip地址转为字符串形式
struct in_addr inet_makeaddr(int net, int host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);
示例一:
服务端:连接客户端并读取客户端IP、发送的信息并返回读取状态。
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int s_fd;
char readBuf[128];
memset(readBuf,0,sizeof(readBuf));
int nread=0;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset(&s_addr,0,sizeof(struct aockaddr_in *));
memset(&s_addr,0,sizeof(struct aockaddr_in *));
s_fd=socket(AF_INET,SOCK_STREAM,0);
if(s_fd==-1)
{
printf("creat soclet failed\n");
perror("socket");
exit(-1);
}
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(9090);
inet_aton("169.254.6.127",&(s_addr.sin_addr));
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
listen(s_fd,10);
int clen=sizeof(struct sockaddr_in);
int c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1)
{
perror("acccept");
}
printf("get connect :%s\n",inet_ntoa(c_addr.sin_addr));
nread=read(c_fd,readBuf,128);
if(nread ==-1)
{
perror("read");
}
else if(nread>0)
{
printf("get message:%d,%s\n",nread,readBuf);
}
else
{
printf("client quit\n");
}
write(c_fd,"hhhhhhhhhhhhhhh",128);
return 0;
}
示例二:
客户端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int c_fd;
char readBuf[128];
memset(readBuf,0,sizeof(readBuf));
int nread=0;
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct aockaddr_in *));
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd==-1)
{
printf("creat soclet failed\n");
perror("socket");
exit(-1);
}
c_addr.sin_family=AF_INET;
c_addr.sin_port=htons(9090);
inet_aton("169.254.6.127",&(c_addr.sin_addr));
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) ==-1)
{
perror("connect");
exit(-1);
}
write(c_fd,"xxxxxxxxx",128);
nread=read(c_fd,readBuf,128);
if(nread ==-1)
{
perror("read");
}
else
{
printf("get message from sever:%d,%s\n",nread,readBuf);
}
return 0;
}