网络套接字socket,利用UDP协议实现服务器与客户端通信
两台主机之间的通信,是通过网卡再经过网络,互相传输,那么我们先来介绍再通信中想要在全球的pc中找到你要发送数据那一台,就需要IP来标识,那么这里在数据报文中就包含了源IP和目的IP,分别标识的是数据从哪来要到那里去。
有了IP那么要怎么认识是主机的哪个进程收数据,这时就有端口号,一个端口号标识着一台主机上的唯一的进程。那么有个问题?
为什么不用PID而要用端口号?因为在一台主机或者服务器上,一个进程的pid随着系统的重启会发生变化,发生变化了,对于客户端没有什么影响,但是对于服务器,客户端就不知道访问的是哪个进程了,所以用一个传输层的端口号,这样就可以通过绑定的端口号来进行访问。
端口号:端口号是一个两个字节的整数 0 ~ 65535
端口号是用来标识一个进程
一台主机要访问另一台主机,那么就需要IP和端口号来标识一台进程
通常情况下,一个端口号只能被一个进程绑定。
TCP/UDP特点
TCP
- 传输层协议
- 有链接
- 可靠传输
- 面向字节流
UDP
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
关于网路中传输的字节序
计算机是分有大端和小端。有这个差异是因为计算机制造厂商,那么在网络中传输也就统一了字节序。
小端机,就是在低地址处存地位
大端机,是在高地址处存地位
而网络中规定为大端传输,所以就要在传输的过程中把一些数值转换成网络字节序来进行传输。
那么我们就要了解一组API
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); // 主机转网络int
uint16_t htons(uint16_t hostshort); // 主机转网络short
uint32_t ntohl(uint32_t netlong); // 网络转主机int
uint16_t ntohs(uint16_t netshort); // 网络转主机short
这里再来个linux下的命令netstat -nap 查看
socket套接字
socket套接字是TCP/IP协议族相关的API,返回值为一个文件描述符,我们先来认识socket函数接口
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数介绍:
domain :是协议是IPV4,还是IPV6,在当今主要还是IPV4,为4个字节,IPV6为16个字节。
type:是使用TCP还是UDP等等一些协议进行传输
protocol:用来指定socket所使用的传输协议编号。这一参数通常不具体设置,一般设置为0
返回值:
返回一个正整数,是文件描述符。通过对文件描述符的操作来进行网络上数据的传输。
前面我们说了,在网络通信中如何标识一个进程,那就是端口号,所以我们在服务器端就会绑定一个端口号,这样就可以标识出一个进程。通过端口号就可以找到对应的进程进行通信。
介绍绑定端口号API
#include <sys/socket.h>
int bind(int sock, const struct sockaddr* address,\
socklen_t address_len);
参数:
sock:为socket返回的文件描述符
address:结构体里面有IP协议,有要绑定的端口号和IP地址,这是一个通用的结构体,因为c语言没有多态,多以就用一个结构体通过强转来实现自己想要的。
address_len:结构体的大小
返回值:
成功返回0,失败返回-1
有了前面的介绍我们来实现一个简单的服务器与客户端。
UDP实现服务器与客户端之间通信
首先我们介绍我们通信的场景~
我们要实现一个网络版的 +、-、*、/ 计算器
通过客户端发来的信息我们返回一个结果,这个其中我们就自定义了应用层的协议来满足自己的需求。
首先我们来试下服务器
headfile
#pragma once
typedef struct Request
{
int a;
int b;
char s;
}Request;
typedef struct Response
{
int sum;
}Response;
server.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include "poto.h"
int main(int argc, char* argv[])
{
if (argc != 3)
{
perror("usage: ./server [ip] [port]\n");
return 1;
}
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
perror("socket error\n");
return 2;
}
// 绑定端口号
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(argv[1]);
server.sin_port = htons(atoi(argv[2]));
int b = bind(sock, (struct sockaddr*)&server, \
sizeof(server));
if (b < 0)
{
perror("bind error\n");
return 3;
}
// recvfrom
Request req; // 自定义的协议来进行对请求和响应进行处理
struct sockaddr_in client;
socklen_t len = sizeof(client);
////////////////////////////////
// 自定义的协议根据实际情况来进行实际的接受的方式与大小,要注意!!!!
////////////////////////////////
// sendto
Response resp;
while (1)
{
// 一般收的函数为输出型参数
ssize_t recv = recvfrom(sock, &req, sizeof(req), 0, \
(struct sockaddr*)&client, &len);
if (recv < 0)
{
perror("recvfrom error\n");
continue;
}
if (recv == 0)
{
printf("read done!\n");
return 0;
}
// 把网络序转成主机序列
req.a = ntohl(req.a);
req.b = ntohl(req.b);
printf("client %s:%d say # %d%c%d\n", \
inet_ntoa(client.sin_addr), \
ntohs(client.sin_port), req.a,req.s, req.b);
// 进行计算
switch(req.s)
{
case '+':resp.sum = req.a + req.b;
break;
case '-':resp.sum = req.a - req.b;
break;
case '*':resp.sum = req.a * req.b;
break;
case '/':resp.sum = req.a / req.b;
break;
default:;
}
printf("server sun = %d\n", resp.sum);
// 主机序转换成网路序,注意是long
resp.sum = htonl(resp.sum);
// 拿到了IP和端口号回发
sendto(sock, &resp, sizeof(resp), 0, \
(struct sockaddr*)&client, len);
}
return 0;
}
client
这里要说明,客户端一般不需要绑定端口号,在发送数据的时候,操作系统会自动分配端口号。
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/socket.h>
#include "poto.h"
// .server 127.0.0.1 9999
int main(int argc, char* argv[])
{
if (argc != 3)
{
perror("usage: ./server [ip] [port]\n");
return 1;
}
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
perror("socket error\n");
return 2;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(argv[1]);
server.sin_port = htons(atoi(argv[2]));
Request req;
Response resp;
while (1)
{
printf("输入要计算的数,用空格隔开,如 1 + 2<Enter>\n");
scanf("%d %c %d", &req.a,&req.s,&req.b);
if (req.s == '/' && req.b == 0)
{
perror("除0非法\n");
continue;
}
req.a = htonl(req.a);
req.b = htonl(req.b);
sendto(sock, &req, sizeof(req), 0, \
(struct sockaddr*)&server, sizeof(server));
ssize_t rd = recvfrom(sock, &resp, sizeof(resp), 0,\
NULL, NULL);
if (rd < 0)
{
perror("recvfrom error\n");
return 3;
}
if (rd == 0)
{
printf("read done!\n");
return 0;
}
resp.sum = ntohl(resp.sum);
printf("sum = %d\n", resp.sum);
}
return 0;
}
我们来看看运行结构
客户端运行
服务器运行
上一篇: Python进阶者笔记(TCP套接字与UDP套接字)
下一篇: Escape(HDU - 3533)