网络套接字编程(UDP)

在了解网络编程之前,我们要先了解一下基础知识。

IP地址

IP协议有两个版本,ipv4和ipv6。通常情况下,在没有特别说明的情况下,我们都是用的ipv4协议。

1、IP地址在IP协议中是用来标识网络中不同主机的地址
2、对于iPhonev来说,IP地址是一个4字节,32位的整数
3、我们通常也用点分十进制的字符串来表示IP地址,列入 192.168.0.1;用点分割的每一个数字代表一个字节,范围是0-255

源IP地址和目的IP地址

但是只有IP地址就可以发送数据了吗?就像你找到某个人家里的地址,但是你并不知道他们家门牌号一样,也是无法正确找到他家的。所以,一样的,我们也需要有一个其他的标识才能区分出这个数据要发给是谁。所以,端口号就出现啦!

端口号(port)

端口号是传输层协议的内容

  • 端口号是一个2字节16位整数
  • 端口号用来标识一个进程,告诉操作系统,当前这个数据要交给哪个进程
  • IP地址+端口号能够识别网络上某一台主机的某个进程
  • 一个端口号只能被一个进程占用
    ####端口号和进程ID区别
    pid是给操作系统内核看的,表示唯一一个进程,而端口号是在网络编程部分唯一标识进程的。那有什么区别呢?
    一个进程可以绑定多个端口号,但是一个端口号不能被多个进程绑定
认识tcp协议

传输层协议
有连接
可靠传输
面向字节流

认识UDP协议

传输层协议
无连接
不可靠传输
面向数据报

网络字节序

  1. 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  2. 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
  3. 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
  4. TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
  5. 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
  6. 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
    用库函数进行网络字节序和主机字节序的转换。

Socket常见API

  1. socket()创建socket 文件描述符 int socket(int domain, int type, int protocol); 第一个参数 默认ipv4 所以用 AF_INET,第二个参数用SOCK_DGRAM表示udp,第三个参数默认是0
  2. 绑定端口号(TCP/UDP,服务器) bind() 把当前的进程和文件描述符关联起来 只用于tcp/udp的服务器
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 第一个参数就是用socket创建的 套接字
  3. 开始监听socket listen() 只用于tcp的服务器
  4. 接收请求 accept() 只用于tcp的服务器
  5. 建立连接 connect() 只用于tcp的客户端
sockaddr 结构

这里写图片描述

###简单的UDP网络程序

//client端
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<string.h>
#include<arpa/inet.h>

//./client [ip][port] 服务器的端口号
typedef struct sockaddr_in sockaddr_in;
typedef struct sockaddr sockaddr;
int main(int argc,char*argv[])
{
    if(argc != 3)// 0 1 2 都被用了
    {
        printf("Usage ./client [ip][port]\n");
        return 1;
    }
    int fd = socket(AF_INET,SOCK_DGRAM,0);
    if(fd < 0 )
    {
        perror("socket");
        return 1;
    }
    sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);
    server_addr.sin_port = htons(atoi(argv[2]));

    //循环  客户端不需要进行bind
    char buf[1024] = {0};
    sockaddr_in peer;
    while(1)
    {
        //从标准输入里读
        socklen_t len = sizeof(peer);
        printf("please Enter# ");
        fflush(stdout);//刷新标准输出
        ssize_t read_size = read(0,buf,sizeof(buf)-1);
        if(read_size < 0)
        {
            perror("read");
            return 1;//这里就直接错误就退出,因为一个服务器只有一个客户端
        }
        buf[read_size] = '\0';
        ssize_t write_size = sendto(fd,buf,strlen(buf),0,(sockaddr*)&server_addr,sizeof(server_addr));
        if(write_size < 0)
        {
            perror("sendto");
            return 1;
        }
        char buf_recv[1024] = {0};
        read_size = recvfrom(fd,buf_recv,sizeof(buf_recv)-1,0,(sockaddr*)&server_addr,&len);
        if(read_size > 0)
        {
            buf[read_size] = '\0';
            printf("server response # %s\n",buf_recv);
        }
    }
}
//server端
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<string.h>
#include<arpa/inet.h>
/*一个服务器程序的典型逻辑
 * 1.初始化(制定IP地址和端口号,加载需要的数据文件)
 * 2.进入事件循环(死循环,无限的等待客户端的请求)
 *  (a)读取客户端发送的数据
 *  (b)根据客户端发送的数据进行计算,对于不同用途的服务器,计算的具体逻辑不同,其中的过程很复杂,涉及到几台甚至几十台机器的配合
 *  (c)根据计算出来的最终结果,拼接响应的字符串。
 *   
*/
int main(int argc,char*argv[])
{
    if(argc != 3)// 0 1 2 都被用了
    {
        printf("Usage ./server [ip][port]\n");
        return 1;
    }
    int fd = socket(AF_INET,SOCK_DGRAM,0);
    if(fd < 0)
    {
        perror("socket");
        return 1;
    }
    //socketaddr_t  
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(argv[1]);//将IP地址转换为端口号
    addr.sin_port = htons(atoi(argv[2]));//将主机序转换为网络序
    //上面的转换就是为了 调用bind 把文件描述符和端口号关联在一起
    int ret = bind(fd,(struct sockaddr*)&addr,sizeof(addr));//这里addr取到的就是端口号
    if(ret < 0)
    {
        perror("bind");
        return 1;
    }
    //对于这个程序来说 这个承租要一直执行下去,就需要一个一直执行的程序,就是需要一个 while循环
    while(1)
    {
        //接收数据
        struct sockaddr_in peer_addr;//对端
        socklen_t len = sizeof(peer_addr);
        char buf[1024] = {0};//初始化
        ssize_t read_size = recvfrom(fd,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer_addr,&len);
        if(read_size < 0)
        {
            perror("recvfrom");//不能因为一个错误就不给其他的端口服务
            continue;
        }
        buf[read_size] = '\0';//双重保证
        printf("client port[%s:%d] %s\n",inet_ntoa(peer_addr.sin_addr),ntohs(peer_addr.sin_port),buf);//又转换为网络字节序inet_ntoa   
    //对于不同的服务器来说,要根据接收到的请求(request),计算出不同的响应(responsse)
    //此处,由于我们只需要实现一个简单的echo服务器,不需要计算了。
    sendto(fd,buf,strlen(buf),0,(struct sockaddr*)&peer_addr,sizeof(peer_addr));//只有前面一部分是有意义的,所以用strlen
    }

}
//Makefile
.PHONY:All
All:client server

client:client.c
    gcc -o $@ $^
server:server.c
    gcc -o $@ $^
.PHONY:clean
clean:
    rm -f client server

运行结果
这里写图片描述

欢迎提出问题。以上仅供参考。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值