Sockt服务器和客户端的开发步骤
TCP
connect()最好建立在listen()后,一旦监听到就建立连接。
UDP
常用API
包含头文件
#include<sys/types.h>
#include<sys/socket.h>
创建套接字(连接协议)
作用
用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源的函数。
函数原型
int socket(int domain,int type, int protocol);
参数解读
domain:指明所使用的协议族/域。
常用的domain类型有:
- AF_INET IPv4——因特网域
- AF_INET6 IPv6——因特网域
- AF UNIX Unix——域
- AF ROUTE——路由套接字
- AFKEYE——钥套接字
- AF UNSPEC——未拖定
type:指定socket类型。
常用的socket类型有:
SOCK_STREAM
TCP:流式套接字提供可靠的、面向连接的通信流:它使用TCP协议,从而保证了数据传输的正确性和顺序性。
SOCK_DGRAM
UDP:数据报套接字定义了一种无连按的服,数据通过相互独立的报文进行传输, 是无序的,并且不保证是可靠,无差错的。它使用数据报协议UDP。
SOCK_RAW
允许程序使用低层协议,原始科接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。
protocol:通常赋值 00
表示选择type类型对应得默认协议。
IPPROTO_TCP
——TCP传输协议IPPTOTO_UDP
——UDP传输协议IPPROTO_SCTP
——STCP传输协议IPPROTO_TIPC
——TIPC传输协议
返回值
成功时返回新的套接字描述符,失败返回 -1。
地址准备(为套接字添加端口号和IP地址)
作用
套接字绑定到一个地址,并制定一个端口号。 将套接字绑定一个IP地址和端口号,因为这两个元素可以在网络环境中唯一地址表示一个进程。
函数原型
int bind(int sockfd, const struct sockaddr *addr,int addrlen);
参数解读
sockfd | 是一个socket描述符。 |
*addr | 是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针,指向要绑定给sockfd的协议地址结构这个地址结构根据地址创建,socket时的地址协议族的不同而不同。 |
addrlen | 第二个参数中结构体的长度。 |
ipv4对应的结构体:
第一种:
struct sockaddr{
unisgned short as family; //协议族 选择TCP/UDP
char sa data[14]; //IP+端口 以字符串形式传递
};
第二种(常用):
比较常用,但调用时需要转换成第一种结构体名字,转换格式如(struct sockaddr *)
struct sockaddr_in{
sa_family_t sin_family; //协议族 一般是IPV4的网络协议
in_port t sin_port; //端口号
struct in_addr sin_addr;//IP地址结构体
unsigned char sin_zero[8];//填充 没有实际意义,只是为跟sockaddh结构在内存中对齐这样两者才能相互转换
};
监听网络连接
作用
listen函数使用主动连接套接字变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
listen函数一般在调用bind之后-调用accept之前调用。
设置处理的最大连接数,listen()并未开始接受连线,只是设置 sockect 的 listen 模式,listen 函数只用于服务器端,服务器进程不知道要与谁连接,因此,它不会主动此要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接清求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。主要就两个功能:将一个未连接的套接字转换为一个被动套接字(监听),规定内核为相应套接字排队的最大连接数。
内核为任何一个给定监听套接字维护两个队列:
(1) 未完成连接队列,每个这样的 SYN 报文段对应其中一项:已由某个客户端发出并到达服务器,而服务正在等待完成相应的 TCP 三次握手过程。这些套接字处于SYN_REVD状态。
(2) 已完成连接队列,每个已完成 TCP 三次握手过程的客户端对应其中一项。这些套接字处于 ESTABLISHED 状态。
函数原型
int listen(SOCKET sockfd, int backlog);
参数解读
sockfd | socket系统调用返回的服务器端socket描述符 |
backlog | 指定在请求队列中允许的最大请求数 |
网络连接
作用
accept函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。
函数原型
int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数解读
sockfd | socket系统调用返回的服务器端socket描述符 |
* addr | 指向客户端数据结构sockaddr的指针,其中包括目的端口和IP地址 |
*addrlen | 参数二sockaddr的长度的地址,可以通过sizeof(struct sockaddr)获得 |
返回值
该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示 TCP 三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。
客户端连接
作用
该函数用于绑定之后的client 端(客户端),与服务器建立连接。(客户机连接主机)
函数原型
int connect (int sockfd, struct sockaddr *addr, socklen_t addrlen);
参数解读
sockfd | socket系统调用返回的服务器端socket描述符 |
* addr | 指向客户端数据结构sockaddr的指针,其中包括目的端口和IP地址 |
addrlen | 参数二sockaddr的长度,可以通过sizeof(struct sockaddr)获得 |
返回值
成功返回0,遇到错误时返回-1,并且errno 中包含相应的错误码。
TCP数据收发(第一套)
(1)发送
函数原型
ssize_ t send(int s, const void *msg,size_t len,int flags);
参数解读
s | s为已建立好连接的套接字描述符 |
* msg | msg指向存放待发送数据的缓冲区 |
len | len为待发送数据的长度 |
flags | flags为控制选项,一般设置为0 |
(2)接收
函数原型
ssize t recv(int s, void *buf,size_t len, int flags);
参数解读
s | s为已建立好连接的套接字描述符 |
* msg | msg指向存放待发送数据的缓冲区 |
len | len为待发送数据的长度 |
flags | flags为控制选项,一般设置为0 |
返回值
函数返回读或写的字节个数,出错则返回-1。
TCP数据收发(第二套)
(1)发送
将buf中的nbytes个字节写入到文件描述符fd中,成功时返回写的字节数。
函数原型
ssize_t write(int fd, const void *buf,size_t nbytes);
参数解读
fd | fd为已建立好连接的文件描述符 |
* buf | buf指向存放待发送数据的缓冲区 |
size_t nbytes | size_t nbytes为待发送数据的长度 |
(2)接收
从fd中读取nbyte个字节到buf中,返回实际所读的的字节数。
函数原型
ssize_t read(int fd,void *buf,size_t nbyte);
参数解读
fd | fd为已建立好连接的文件描述符 |
* buf | buf指向存放待发送数据的缓冲区 |
size_t nbytes | size_t nbytes为待发送数据的长度 |
UDP数据收发(待完善)
UDP数据收发与TCP类似,常用的API是recvmsg()/sendmsg(),revfrom()/sendto0。
地址转换API
字符串转网络
int inet_aton(const char* straddr,struct in_addr *addrp);
作用:把字符串形式的”192.168.1.123"转为网络能识别的格式
网络转字符串
char* inet ntoa(struct in_ addr inaddr);
作用:把网络格式的ip地址转为字符串形式
代码示例
1、客户端与网络实现连接
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
int mian()
{
int s_fd;//定义一个套接字描述符
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1)//返回-1时表示创建失败
{
perror("socket");
exit(-1);
}
struct sockaddr_in s_addr;//定义一个sockaddr_in类型的结构体(后面需要转为sockaddr类型)
s_addr.sin_family = AF_INET;//IPV4的英特网域
s_addr.sin_port = htons(8888);//端口号 x86主机是小端 网络是大段 需进行转换
inet_aton("127.0.0.1",&s_addr.sin_addr);//把字符串形式的”127.0.0.1”转为网络能识别的格式
//127.0.0.1表示本机地址 ifconfig查询电脑地址
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//需要注意第二个参数需将原来的sockaddr_in结构体类型转换成sockaddr结构体类型
listen(s_fd,10);
int c_fd = accept(s_fd,NULL,NULL);//需定义一个fd来接受其返回值 后续对连接的操作是基于fd 若没有接收到数据 则会一直阻塞
printf("connect\n");
while(1);
return 0;
}
编译结果:编译运行后,程序会一直阻塞,打开另一执行程序页面,输入 telnet+电脑地址后 程序接收成功,则会输出connect。
2、客户端连接实现信息交流
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int s_fd;
int n_read;//用于判断
char readBuf[128];
char *msg = "I get your connect";
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset(&s_addr,0,sizeof(struct sockaddr_in));//对数据进行清空再定义
memset(&c_addr,0,sizeof(struct sockaddr_in));
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1)
{
perror("socket");
exit(-1);
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8888);
inet_aton("127.0.0.1",&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);//计算结构体c_addr的大小
int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1)
{
perror("accept");
exit(-1);
}
printf("get connect :%s\n",inet_ntoa(c_addr.sin_addr));//将网络ip地址转为字符串形式 连接成功先输出此行
n_read = read(c_fd,readBuf,128);//从c_fd读取128个字节到readBuf中
if(n_read == -1)//调用失败返回-1
{
perror("read");
}
else
{
printf("get message:%d,%s\n",n_read,readBuf);
}
write(c_fd,msg,strlen(msg));
return 0;
}
编译运行后,打开另一程序运行界面,输入telnet+电脑地址后 程序接收成功,则会输出connect并将IP地址以字符串形式打印出来,同时输入相应数据另一端会接收成功,实现客户端与客户端信息交流。