在一次服务中提供数据或服务的称为服务器,获取数据的称为客户端。
在网络基础中讲到有许多协议,那这里就面临协议的选择,TCP还是UDP?
先来看看TCP和UDP的区别:
TCP:
面向连接的(服务器和客户端之间维护一条专有线路)
可靠的(数据无差错、不丢失、不重复、并且按序到达)
流式服务
UDP:
无连接(每次都要探寻线路)
不可靠的
数据报服务
线路连接上以后,未来时间段不间断发送数据,选择TCP。但是维护连接线路要付出代价,根据
数据是否重要来选择可靠还是不可靠,有些数据不一定要求可靠。
两种协议自然就有两种编程方式:
TCP编程流程:
- 服务器端sever:
- 创建socket
- bind(将IP地址和端口号命名,一般我们访问网站都不是直接输IP地址的吧)
- listen(监听连接,创建监听队列)
- accept(从listen拿到已经完成连接的)
- recv/send(收发数据)
- close(关闭连接,所有完成后再关闭一次socket)
- 客户端client:
- 创建socket ()/* bind 一般不要*/
- 发起连接connect
- recv/send(收发数据)
- close
具体函数如下:
1、创建socket
int socket(int domain, int type, int protocol);
//domain 协议簇 IPv4(AF_INET)/ IPv6(AF_INET6);
//type 选择TCP(SOCK_STREAM)还是UDP(SOCK_DGRAM);
//protocol TCP下具体的哪个协议,现在已经不用,给0;
//成功返回一个socket文件描述符,失败返回-1;
2、命名socket
int bind(int sockfd,const struct sockaddr*addr,int addrlen); //sockfd socket函数返回的文件描述符; //addr 指定IP地址和端口号 //addrlen 该socket地址的长度
//成功返回0,失败返回-1并设置erron。常见erron是EACCESS(被绑定的地址是受保护的地址,即小于1024,仅超级用户能够访问)EADDRINUSE(被绑定的地址正在使用中)
struct sockaddr_in{
sa_family_t sin_family; /*地址族:AF_INET*/
u_int16_t sin_port; /*端口号(网络字节序:大端模式)*/
struct in_addr sin_addr; /*IPv4地址结构体*/
};
struct sin_addr{
u_int32_t s_addr; /*IPv4地址,用网络字节序表示*/
};
所有专用socket地址类型的变量在实际使用时都需要强制转换成通用的sockaddr地址类型,因为所有socket编程接口使用的地址参数类型都是sockaddr。当然
在这里也要弄清楚主机字节序和网络字节序。
通常都是用点分十进制字符串表示IPv4地址,但是编程中需要转换成整数(二进制)使用。系统提供了3个函数用于,点分十进制字符串表示的IPv4地址和用网络字节序整数表示的IPv4地址之间的转换:
#include<arpa/inet.h> int_addr_t inet_addr(const char*str);
//将点分十进制字符串表示的IPv4地址转换成用网络字节序整数表示的IPv4地址,失败时返回INADDR_NONE,方便
int inet_aton(const char*cp,struct in_addr *inp);
//将点分十进制字符串表示的IPv4地址转换成用网络字节序整数表示的IPv4地址, 将转化结果储存于参数inp指向的地址结构中,成功返回1,失败返回0.相比于inet_addr更安全
char *inet_ntoa(struct in_addr in);
3、监听socket//将用网络字节序整数表示的IPv4地址转换成点分十进制字符串表示的IPv4地址,该函数不可重入
int listen(int sockfd, int backlog); //创建一个监听队列,有连接放到队列中,backlog表示队列大小,但是底层有默认值,给的小了就用默认值
//成功返回0,失败返回-1
4、接收连接
int accept(int sockfd,struct sockaddr*addr,int len);
//sockfd 执行过listen监听的socket
//addr 记录客户端的IP地址和端口号
//len 长度
//成功返回获取到的和客户端连接的文件描述符,此后由该返回值和客户端通信,失败返回-1
5、发起连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); //addr 要连接的服务器的IP地址和端口号
6、关闭连接//成功返回0,失败返回-1
int close(int fd);
//fd 待关闭的socket
7、收发数据
#include<sys/typs.h> #include<sys/socket.h> ssize_t recv(int sockfd, void*buf, size_t len, int flags); //接受(读取)sockfd上的数据,放到buf缓冲区,大小为len,flags暂且设为0; //成功返回实际读取到的数据长度,返回0说明对方已关闭连接,出错返回-1; ssize_t send(int sockfd, const void*buf, size_t len, int flags); //buf缓冲区的数据发送(写入)sockfd,大小为len,flags暂且设为0; //成功返回实际读写入数据长度,出错返回-1;
既然要完成通信,那么至少有一个服务器端一个客户端,代码如下:
//服务器端ser.c
void main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in ser,cli;
ser.sin_family = AF_INET;
ser.sin_port = htons(6000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res != -1);
listen(sockfd,5);
int len = sizeof(cli);
int c = accept(sockfd,(struct sockaddr*)&cli,&len);
assert(c != -1);
char buff[128] = {0};
recv(c,buff,127,0);
printf("recv::%s\n",buff);
send(c,"I Know",strlen("I Know"),0);
close(c);
close(sockfd);
}
//客户端cli.c
void main()
{
int sockfd = socket(AF_INET,SOCK_STREAN,0);
assert(sockfd != -1);
struct sockaddr_in ser,cli;
ser.sin_family = AF_INET;
ser.sin_port = htons(6000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = connect(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res != -1);
send(sockfd,"Hello World",strlen("Hello World"),0);
char buff[128] = {0};
recv(sockfd,buff,127,0);
printf("recv::%s\n",buff);
close(sockfd);
}
但是在实际应用中,服务器发送一次并没有结束,客户端可以一直申请,因此服务器端要循环收发数据。
void main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in ser,cli;
ser.sin_family = AF_INET;
ser.sin_port = htons(6000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res != -1);
listen(sockfd,5);
while(1)
{
int len = sizeof(cli);
int c = accept(sockfd,(struct sockaddr*)&cli,&len);
assert(c != -1);
char buff[128] = {0};
recv(c,buff,127,0);
printf("recv::%s\n",buff);
send(c,"I Know",strlen("I Know"),0);
close(c);
}
close(sockfd);
}