国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字&UDP服務(wù)器客戶端的簡單模擬

這篇具有很好參考價(jià)值的文章主要介紹了【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字&UDP服務(wù)器客戶端的簡單模擬。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

需要云服務(wù)器等云產(chǎn)品來學(xué)習(xí)Linux的同學(xué)可以移步/–>騰訊云<–/官網(wǎng),輕量型云服務(wù)器低至112元/年,優(yōu)惠多多。(聯(lián)系我有折扣哦)

1. 前置知識(shí)

1.1 源IP和目的IP

每臺(tái)主機(jī)都有自己的IP地址,當(dāng)數(shù)據(jù)在進(jìn)行通信的時(shí)候,除了要發(fā)送的數(shù)據(jù)外,在報(bào)頭里面還要包含發(fā)送方的IP和接收方的IP,這里發(fā)送方的IP就被稱為源IP,接收方的IP就是目的IP

現(xiàn)在有了源IP和目的IP之后,是不是就能夠通信了呢?

源IP和目的IP的出現(xiàn)只能標(biāo)識(shí)兩臺(tái)主機(jī)的唯一性,但是在主機(jī)上還存在很多個(gè)進(jìn)程,在發(fā)送數(shù)據(jù)的時(shí)候肯定是由一個(gè)進(jìn)程發(fā)送給另一個(gè)進(jìn)程的,所以還需要標(biāo)識(shí)兩臺(tái)主機(jī)上進(jìn)程的唯一性

為了更好的表示一臺(tái)主機(jī)上服務(wù)進(jìn)程的唯一性,用端口號(hào)port標(biāo)識(shí)服務(wù)進(jìn)程、客戶端進(jìn)程的唯一性。

1.2 端口號(hào)

端口號(hào)(port)是傳輸層協(xié)議的內(nèi)容

  • 端口號(hào)是一個(gè)2字節(jié)16位的整數(shù)
  • 端口號(hào)用來標(biāo)識(shí)一個(gè)進(jìn)程, 告訴操作系統(tǒng), 當(dāng)前的這個(gè)數(shù)據(jù)要交給哪一個(gè)進(jìn)程來處理
  • IP地址 + 端口號(hào)能夠標(biāo)識(shí)網(wǎng)絡(luò)上的某一臺(tái)主機(jī)的某一個(gè)進(jìn)程
  • 一個(gè)端口號(hào)只能被一個(gè)進(jìn)程占用,一個(gè)進(jìn)程可以占用多個(gè)端口號(hào)

由上面可知:IP地址(主機(jī)的全網(wǎng)唯一性)+該主機(jī)上的端口號(hào)(主機(jī)上進(jìn)程唯一性)可以標(biāo)識(shí)一個(gè)唯一的進(jìn)程。那么最終網(wǎng)絡(luò)通信的本質(zhì)就是進(jìn)程間通信

進(jìn)程已經(jīng)有pid標(biāo)識(shí)唯一性了,為什么還要有端口號(hào)呢?

  • 系統(tǒng)是系統(tǒng),網(wǎng)絡(luò)是網(wǎng)絡(luò),單獨(dú)設(shè)置為了讓網(wǎng)絡(luò)和系統(tǒng)解耦
  • 客戶端需要每次都能夠找到服務(wù)端進(jìn)程,這就要求了服務(wù)端的唯一性不能發(fā)生任何改變,pid在每次進(jìn)程創(chuàng)建的時(shí)候都有可能改變
  • 不是所有進(jìn)程都需要進(jìn)行網(wǎng)絡(luò)通信的(需要端口號(hào)),但是所有進(jìn)程都需要pid。

1.3 TCP協(xié)議和UDP協(xié)議初識(shí)

1. TCP協(xié)議

此處我們先對(duì)TCP(Transmission Control Protocol 傳輸控制協(xié)議)有一個(gè)直觀的認(rèn)識(shí); 后面我們?cè)僭敿?xì)討論TCP的一些細(xì)節(jié)問題.

  • 傳輸層協(xié)議
  • 有連接
  • 可靠傳輸
  • 面向字節(jié)流

2. UDP協(xié)議

UDP(User Datagram Protocol 用戶數(shù)據(jù)報(bào)協(xié)議

  • 傳輸層協(xié)議
  • 無連接
  • 不可靠傳輸
  • 面向數(shù)據(jù)報(bào)

1.4 網(wǎng)絡(luò)字節(jié)序

在之前的文章中數(shù)據(jù)在內(nèi)存中的存儲(chǔ),我們提到了數(shù)據(jù)內(nèi)存中存儲(chǔ)的時(shí)候有大端小端之分,磁盤文件中的多字節(jié)數(shù)據(jù)相對(duì)于文件中的偏
移地址也有大端小端之分。

大端機(jī)器和和小端機(jī)器也有可能通信,那么這個(gè)時(shí)候如何定義網(wǎng)絡(luò)通信的數(shù)據(jù)流呢?

發(fā)送主機(jī)通常將發(fā)送緩沖區(qū)中的數(shù)據(jù)按內(nèi)存地址從低到高的順序發(fā)出;接收主機(jī)把從網(wǎng)絡(luò)上接到的字節(jié)依次保存在接收緩沖區(qū)中,也是按內(nèi)存地址從低到高的順序保存;因此,網(wǎng)絡(luò)數(shù)據(jù)流的地址應(yīng)這樣規(guī)定:先發(fā)出的數(shù)據(jù)是低地址,后發(fā)出的數(shù)據(jù)是高地址.TCP/IP協(xié)議規(guī)定,網(wǎng)絡(luò)數(shù)據(jù)流應(yīng)采用大端字節(jié)序,即低地址高字節(jié).不管這臺(tái)主機(jī)是大端機(jī)還是小端機(jī), 都會(huì)按照這個(gè)TCP/IP規(guī)定的網(wǎng)絡(luò)字節(jié)序來發(fā)送/接收數(shù)據(jù);如果當(dāng)前發(fā)送主機(jī)是小端, 就需要先將數(shù)據(jù)轉(zhuǎn)成大端; 否則就忽略, 直接發(fā)送即可

為使網(wǎng)絡(luò)程序具有可移植性,使同樣的C代碼在大端和小端計(jì)算機(jī)上編譯后都能正常運(yùn)行,可以調(diào)用以下庫函數(shù)做網(wǎng)絡(luò)字節(jié)序和主機(jī)字節(jié)序的轉(zhuǎn)換

#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);

實(shí)際上這些函數(shù)名是很好記的,按照作用來組合即可,其中h表示host,是當(dāng)前主機(jī)的意思;n表示network,是網(wǎng)絡(luò)的意思;l表示32位長整數(shù),s表示16位短整數(shù)

例如:htonl表示將32位的長整數(shù)從主機(jī)字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序,例如將IP地址轉(zhuǎn)換后準(zhǔn)備發(fā)送。如果主機(jī)是小端字節(jié)序,這些函數(shù)將參數(shù)做相應(yīng)的大小端轉(zhuǎn)換然后返回;如果主機(jī)是大端字節(jié)序,這些 函數(shù)不做轉(zhuǎn)換,將參數(shù)原封不動(dòng)地返回

1.5 socket編程接口

1.5.1 socket常見API

// 創(chuàng)建 socket 文件描述符(TCP/UDP,客戶端+服務(wù)器)
int socket(int domain, int type, int protocol);
// 綁定端口號(hào)(TCP/UDP,服務(wù)器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
// 開始監(jiān)聽socket (TCP, 服務(wù)器)
int listen(int socket, int backlog);
// 接收請(qǐng)求 (TCP, 服務(wù)器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立連接 (TCP, 客戶端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

1.5.2 sockaddr結(jié)構(gòu)

套接字不僅支持跨網(wǎng)絡(luò)通信,還支持本地的進(jìn)程間通信(域間套接字)。對(duì)于不同的通信方式,需要使用的接口在細(xì)節(jié)上是有一些不同的。所以套接字提供了兩個(gè)結(jié)構(gòu)體sockaddr_insockaddr_un結(jié)構(gòu)體,其中前者是用于跨網(wǎng)絡(luò)通信的,后者用于本地進(jìn)程間通信。但是除此之外,通信的方法基本都是相同的,所以為了讓兩種通信方式能使用同一套函數(shù)接口,所以就定義了sockaddr結(jié)構(gòu)體,該結(jié)構(gòu)體與sockaddr_insockaddr_un的結(jié)構(gòu)都不相同,但這三個(gè)結(jié)構(gòu)體頭部的16個(gè)比特位都是一樣的,這個(gè)字段叫做協(xié)議家族。

三個(gè)結(jié)構(gòu)體的內(nèi)容如下:

【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字&UDP服務(wù)器客戶端的簡單模擬,計(jì)算機(jī)網(wǎng)絡(luò),網(wǎng)絡(luò),計(jì)算機(jī)網(wǎng)絡(luò),udp

所以當(dāng)我們?cè)趥鲄⒌臅r(shí)候,就直接傳sockaddr結(jié)構(gòu)體,在函數(shù)內(nèi)部做識(shí)別,執(zhí)行對(duì)應(yīng)的操作。

注意: 實(shí)際我們?cè)谶M(jìn)行網(wǎng)絡(luò)通信時(shí),定義的還是sockaddr_in這樣的結(jié)構(gòu)體,只不過在傳參時(shí)需要將該結(jié)構(gòu)體的地址類型進(jìn)行強(qiáng)轉(zhuǎn)為sockaddr*罷了。

既然傳遞的類型是指針類型,那么為什么不用C語言的void *類型,而進(jìn)行了這么復(fù)雜的設(shè)計(jì)?

在網(wǎng)絡(luò)通信的結(jié)構(gòu)進(jìn)行設(shè)計(jì)的時(shí)候,C語言的標(biāo)準(zhǔn)還沒有出來。所以進(jìn)行了設(shè)計(jì),在之后為了向前兼容,就沿用了這套接口

2. 簡單的UDP服務(wù)端和客戶端代碼實(shí)現(xiàn)

這里我們采用C和C++混編的方式,就封裝成類來實(shí)現(xiàn),通過makefile進(jìn)行自動(dòng)化編譯。

2.1 makefile文件的編寫

# 這是構(gòu)建本項(xiàng)目的makefile文件

cc=g++ # 這里cc是一個(gè)變量,表示構(gòu)建項(xiàng)目使用的編譯器

.PHONY:all # 構(gòu)建一個(gè)偽對(duì)象,表示我們要同時(shí)構(gòu)建兩個(gè)目標(biāo)文件
all: udpServer udpClient

# 這里是兩個(gè)目標(biāo)文件的依賴和構(gòu)建方法
udpServer:udpServer.cc 
		$(cc) -o $@ $^ -std=c++11
udpClient:udpClient.cc 
		$(cc) -o $@ $^ -std=c++11

# 刪除所有產(chǎn)生的目標(biāo)文件
.PHONY:clean
clean:	
	rm -f udpServer udpClient 

2.2 server端的編寫

2.2.1 需要調(diào)用的函數(shù)

1. socket

頭文件:
    #include <sys/types.h>
    #include <sys/socket.h>
函數(shù)原型:
	int socket(int domain, int type, int protocol);
參數(shù)解釋:
	domain:表示通信類型(本質(zhì)是一個(gè)宏),socket支持多種通信方式,一般來說有本地和網(wǎng)絡(luò)兩種。AF_INET表示使用IPv4進(jìn)行網(wǎng)絡(luò)通信;AF_UNIX表示本地通信。詳情見man手冊(cè)
	type:表示套接字提供服務(wù)的類型,如SOCK_STREAM:流式服務(wù)(TCP策略);SOCK_DGRAM:數(shù)據(jù)報(bào)服務(wù)(UDP策略)
	protocol:表示對(duì)應(yīng)的協(xié)議,實(shí)際上可以由前兩個(gè)參數(shù)確定,所以這里設(shè)計(jì)成0沒有問題
函數(shù)描述:
	創(chuàng)建一個(gè)用于網(wǎng)絡(luò)通信的文件描述符
返回值:
	調(diào)用成功返回文件描述符,否則返回-1同時(shí)設(shè)置錯(cuò)誤碼

2. bind

頭文件:
    #include <sys/types.h>
    #include <sys/socket.h>
函數(shù)原型:
	int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
參數(shù)解釋:
	sockfd:需要設(shè)置的sockfd
	addr:將需要設(shè)置的內(nèi)容對(duì)應(yīng)的結(jié)構(gòu)體指針強(qiáng)轉(zhuǎn)為struct sockaddr *再傳入(為了統(tǒng)一接口)
	addrlen:傳入的addr對(duì)應(yīng)的變量大小
函數(shù)描述:
	將local設(shè)置到內(nèi)核中。
返回值:
	調(diào)用成功返回0,否則返回-1,同時(shí)設(shè)置錯(cuò)誤碼

為什么要使用bind,不能直接通信嗎?

通信是要使用網(wǎng)絡(luò)的,最終也是要通過OS進(jìn)行通信。所以相關(guān)信息需要讓OS來維護(hù),我們沒有辦法直接操作OS內(nèi)核數(shù)據(jù)結(jié)構(gòu),因此需要通過系統(tǒng)調(diào)用bind來將相關(guān)內(nèi)容設(shè)置進(jìn)內(nèi)核

sockaddr相關(guān)結(jié)構(gòu)的定義:

  1. sockaddr的定義:
typedef unsigned short int sa_family_t;

#define	__SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family

struct sockaddr
  {
    __SOCKADDR_COMMON (sa_);
    char sa_data[14];
  };

所以可以看出來sockaddr是一個(gè)結(jié)構(gòu)體,內(nèi)部有兩個(gè)成員變量;其中第一個(gè)變量是16位的地址類型,一個(gè)14位的地址數(shù)據(jù)。__SOCKADDR_COMMON是一個(gè)宏,作用是將括號(hào)內(nèi)的內(nèi)容拼接到family前面,最終形成一個(gè)sa_family_t類型的變量

  1. sockaddr_in的定義
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_); // 定義sin_family變量,16位的數(shù)據(jù)類型
    in_port_t sin_port;	// 16端口號(hào)
    struct in_addr sin_addr; // 32位IP地址

    /* 數(shù)據(jù)填充(無意義)  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
			   __SOCKADDR_COMMON_SIZE -
			   sizeof (in_port_t) -
			   sizeof (struct in_addr)];
  };

3. bzero

頭文件:
	#include <strings.h>
函數(shù)原型:
	void bzero(void *s, size_t n);
參數(shù)解釋:
	s:需要設(shè)置的變量的地址
	n:變量的長度
函數(shù)描述:
	從s開始的n個(gè)字節(jié)設(shè)置為0,類似于memset

4. inet_aton

頭文件:
   #include <sys/socket.h>
   #include <netinet/in.h>
   #include <arpa/inet.h>
函數(shù)原型與描述:
   int inet_aton(const char *cp, struct in_addr *inp); // 將點(diǎn)分十進(jìn)制的IP地址轉(zhuǎn)換為符合網(wǎng)絡(luò)字節(jié)序的二進(jìn)制形式存放到in_addr結(jié)構(gòu)體中
參數(shù)解釋:
	cp:點(diǎn)分十進(jìn)制形式的C式ip字符串
	inp:存放網(wǎng)絡(luò)字節(jié)序的二進(jìn)制i形式IP
返回值:
	如果地址有效,則返回非0,如果地址無效則返回0

5. recvfrom

頭文件:
	#include <sys/types.h>
   	#include <sys/socket.h>
函數(shù)原型:
   ssize_t recvfrom(int sockfd, void *buf, size_t len, int falgs, struct sockaddr *src_addr, socklen_t *addrlen);
函數(shù)描述:
	接收信息
參數(shù)解釋:
	sockfd:用于接收的sockfd
	buf:
	len:
	flags:讀取的方式,默認(rèn)為0,阻塞讀取
	src_addr:收到消息除了本身,還得知道是誰發(fā)的,輸入輸出型參數(shù),返回對(duì)應(yīng)的消息內(nèi)容是從哪一個(gè)client發(fā)來的
	addrlen:結(jié)構(gòu)體大小
返回值:
	返回接收到的字節(jié)數(shù),出錯(cuò)返回-1同時(shí)設(shè)置錯(cuò)誤碼。如果發(fā)送方正常關(guān)閉,返回0

2.2.2 代碼

/*udpServer.hpp*/
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <string.h>
#include <cerrno>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

namespace Server
{
    using func_t = std::function<void(std::string, uint16_t, std::string)>;
    static void Usage()
    {
        std::cout << "\nUsage:\n\t./udpServer local_port\n\n";
    }
    enum // 枚舉出錯(cuò)類型
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR
    };
    const static std::string defaultIP = "0.0.0.0";
    const int gnum = 1024; // 處理任務(wù)的緩沖區(qū)大小
    class udpServer
    {
    public:
        udpServer(const func_t &cb, const uint16_t &port, const std::string &ip = defaultIP)
            : _port(port), _ip(ip) {}
        // 這里初始化要做的事情有兩件: 1. 創(chuàng)建sockfd 2.bind端口號(hào)和ip
        void initServer()
        {
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 參數(shù)1:這里使用AF_INET表示使用IPv4進(jìn)行網(wǎng)絡(luò)通信;參數(shù)2:我們這里使用UDP策略;參數(shù)3:這里使用0表示默認(rèn)
            if (_sockfd == -1)                        // 差錯(cuò)處理
            {
                std::cerr << "socket error " << errno << strerror(errno) << std::endl;
                exit(SOCKET_ERR);
            }
            std::cout << "socket sucess : " << _sockfd << std::endl;

            // 在當(dāng)前函數(shù)的棧幀上創(chuàng)建一個(gè)local對(duì)象,設(shè)置相關(guān)屬性,然后將相關(guān)屬性bind到系統(tǒng)內(nèi)核中
            struct sockaddr_in local;                       // 這里struct sockaddr_in類型需要頭文件arpa/inet.h
            bzero(&local, sizeof(local));                   // 在填充數(shù)據(jù)之前首先將對(duì)象內(nèi)部元素清空,這里使用bzero
            local.sin_family = AF_INET;                     // 設(shè)定協(xié)議家族
            local.sin_port = htons(_port);                  // 設(shè)置端口號(hào),這里端口號(hào)需要首先轉(zhuǎn)換成網(wǎng)絡(luò)端口號(hào)
            local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 設(shè)置ip,這里的ip是string類型,但是實(shí)際在傳輸?shù)臅r(shí)候使用的是整型,所以需要轉(zhuǎn)換,這里使用inet_addr
            // inet_addr的作用有兩個(gè): 1.string -> uint32_t; 2. htonl()
            int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local)); // 將local設(shè)置到內(nèi)核中,即bind
            if (n == -1)
            {
                std::cerr << "bind error " << errno << strerror(errno) << std::endl;
                exit(BIND_ERR);
            }
            // 至此初始化的操作完成
        }
        void start() // 讓服務(wù)器開始跑起來
        {
            // 服務(wù)器的本質(zhì)是一個(gè)死循環(huán),在循環(huán)內(nèi)部處理收到的任務(wù)
            char buffer[gnum];
            while (true)
            {
                // 1. 讀取數(shù)據(jù)
                struct sockaddr_in peer; // 定義一個(gè)變量用于接收數(shù)據(jù)
                socklen_t len = sizeof(peer);
                ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
                // a. 數(shù)據(jù)是什么 b. 誰發(fā)的
                if (n > 0)
                {
                    buffer[n] = 0;
                    std::string clientIp = inet_ntoa(peer.sin_addr); // 轉(zhuǎn)換網(wǎng)絡(luò)字節(jié)序, 點(diǎn)分十進(jìn)制
                    uint16_t clientPort = ntohs(peer.sin_port);
                    std::string message = buffer;

                    std::cout << clientIp << "[" << clientPort << "]# " << message << std::endl;
                    // 2. 處理任務(wù)
                    _callback(clientIp, clientPort, message);
                }
            }
        }

    private:
        // 成員變量分析:作為一個(gè)服務(wù)端進(jìn)程,我們首先需要一個(gè)端口號(hào)port和一個(gè)本地ip
        // 還需要有一個(gè)文件描述符sockfd,用于進(jìn)行通信(網(wǎng)絡(luò)通信是基于文件的,所以使用的都是文件的一套內(nèi)容,包括fd)
        int _sockfd;     // socket文件描述符
        std::string _ip; // 本地ip
        uint16_t _port;  // 服務(wù)進(jìn)程端口號(hào)

        func_t _callback;
    };
}

2.2.3 測試

【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字&UDP服務(wù)器客戶端的簡單模擬,計(jì)算機(jī)網(wǎng)絡(luò),網(wǎng)絡(luò),計(jì)算機(jī)網(wǎng)絡(luò),udp

查看網(wǎng)絡(luò)情況就可以用指令netstat:

  • -a:顯示所有連線中的Socket;
  • -e:顯示網(wǎng)絡(luò)其他相關(guān)信息;
  • -i:顯示網(wǎng)絡(luò)界面信息表單;
  • -l:顯示監(jiān)控中的服務(wù)器的Socket;
  • -n:直接使用ip地址(數(shù)字),而不通過域名服務(wù)器;
  • -p:顯示正在使用Socket的程序識(shí)別碼和程序名稱;
  • -t:顯示TCP傳輸協(xié)議的連線狀況;
  • -u:顯示UDP傳輸協(xié)議的連線狀況;

【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字&UDP服務(wù)器客戶端的簡單模擬,計(jì)算機(jī)網(wǎng)絡(luò),網(wǎng)絡(luò),計(jì)算機(jī)網(wǎng)絡(luò),udp

那么本地測試沒有問題,這里使用的是云服務(wù)器,能不能bind公網(wǎng)IP呢?

./udpServer 8080 公網(wǎng)IP

【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字&UDP服務(wù)器客戶端的簡單模擬,計(jì)算機(jī)網(wǎng)絡(luò),網(wǎng)絡(luò),計(jì)算機(jī)網(wǎng)絡(luò),udp

云服務(wù)器是虛擬化的服務(wù)器,不能直接bind你的公網(wǎng)IP,可以綁定內(nèi)網(wǎng)IP(ifconfig顯示的);如果是虛擬機(jī)或者獨(dú)立真實(shí)的Linux環(huán)境,你可以bind你的IP;

  • 如何保證云服務(wù)器能夠被別人訪問?

    實(shí)際上,一款網(wǎng)絡(luò)服務(wù)器不建議指明一個(gè)IP,也就是不要顯示地綁定IP。服務(wù)器IP可能不止一個(gè),如果只綁定一個(gè)明確的IP,最終的數(shù)據(jù)可能用別的IP來訪問端口號(hào),訪問不出來,所以真實(shí)的服務(wù)器IP一般采用INADDR_ANY(全0,任意地址)代表任意地址bind

最終代碼:

/*udpServer.hpp*/
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <string.h>
#include <cerrno>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

namespace Server
{
    using func_t = std::function<void(std::string, uint16_t, std::string)>;
    static void Usage()
    {
        std::cout << "\nUsage:\n\t./udpServer local_port\n\n";
    }
    enum // 枚舉出錯(cuò)類型
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR
    };
    const static std::string defaultIP = "0.0.0.0";
    const int gnum = 1024; // 處理任務(wù)的緩沖區(qū)大小
    class udpServer
    {
    public:
        udpServer(const func_t &cb, const uint16_t &port, const std::string &ip = defaultIP)
            : _port(port), _ip(ip) {}
        // 這里初始化要做的事情有兩件: 1. 創(chuàng)建sockfd 2.bind端口號(hào)和ip
        void initServer()
        {
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 參數(shù)1:這里使用AF_INET表示使用IPv4進(jìn)行網(wǎng)絡(luò)通信;參數(shù)2:我們這里使用UDP策略;參數(shù)3:這里使用0表示默認(rèn)
            if (_sockfd == -1)                        // 差錯(cuò)處理
            {
                std::cerr << "socket error " << errno << strerror(errno) << std::endl;
                exit(SOCKET_ERR);
            }
            std::cout << "socket sucess : " << _sockfd << std::endl;

            // 在當(dāng)前函數(shù)的棧幀上創(chuàng)建一個(gè)local對(duì)象,設(shè)置相關(guān)屬性,然后將相關(guān)屬性bind到系統(tǒng)內(nèi)核中
            struct sockaddr_in local;                       // 這里struct sockaddr_in類型需要頭文件arpa/inet.h
            bzero(&local, sizeof(local));                   // 在填充數(shù)據(jù)之前首先將對(duì)象內(nèi)部元素清空,這里使用bzero
            local.sin_family = AF_INET;                     // 設(shè)定協(xié)議家族
            local.sin_port = htons(_port);                  // 設(shè)置端口號(hào),這里端口號(hào)需要首先轉(zhuǎn)換成網(wǎng)絡(luò)端口號(hào)
            // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 設(shè)置ip,這里的ip是string類型,但是實(shí)際在傳輸?shù)臅r(shí)候使用的是整型,所以需要轉(zhuǎn)換,這里使用inet_addr
            local.sin_addr.s_addr = INADDR_ANY;
            // inet_addr的作用有兩個(gè): 1.string -> uint32_t; 2. htonl()
            int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local)); // 將local設(shè)置到內(nèi)核中,即bind
            if (n == -1)
            {
                std::cerr << "bind error " << errno << strerror(errno) << std::endl;
                exit(BIND_ERR);
            }
            // 至此初始化的操作完成
        }
        void start() // 讓服務(wù)器開始跑起來
        {
            // 服務(wù)器的本質(zhì)是一個(gè)死循環(huán),在循環(huán)內(nèi)部處理收到的任務(wù)
            char buffer[gnum];
            while (true)
            {
                // 1. 讀取數(shù)據(jù)
                struct sockaddr_in peer; // 定義一個(gè)變量用于接收數(shù)據(jù)
                socklen_t len = sizeof(peer);
                ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
                // a. 數(shù)據(jù)是什么 b. 誰發(fā)的
                if (n > 0)
                {
                    buffer[n] = 0;
                    std::string clientIp = inet_ntoa(peer.sin_addr); // 轉(zhuǎn)換網(wǎng)絡(luò)字節(jié)序, 點(diǎn)分十進(jìn)制
                    uint16_t clientPort = ntohs(peer.sin_port);
                    std::string message = buffer;

                    std::cout << clientIp << "[" << clientPort << "]# " << message << std::endl;
                    // 2. 處理任務(wù)
                    _callback(clientIp, clientPort, message);
                }
            }
        }

    private:
        // 成員變量分析:作為一個(gè)服務(wù)端進(jìn)程,我們首先需要一個(gè)端口號(hào)port和一個(gè)本地ip
        // 還需要有一個(gè)文件描述符sockfd,用于進(jìn)行通信(網(wǎng)絡(luò)通信是基于文件的,所以使用的都是文件的一套內(nèi)容,包括fd)
        int _sockfd;     // socket文件描述符
        std::string _ip; // 本地ip
        uint16_t _port;  // 服務(wù)進(jìn)程端口號(hào)

        func_t _callback;
    };
}
/*udpServer.cc*/
#include "udpServer.hpp"
#include <memory>

using namespace Server;

void handleMessage(std::string clientIp, uint16_t clientPort, std::string message)
{
    std::cout << "我是一個(gè)回調(diào)函數(shù)執(zhí)行,收到的消息是:" << message << std::endl;
}

//  調(diào)用的指令 :./udpServer port
int main(int argc, char *argv[])
{
    // 解析指令
    if (argc != 2)
    {
        Usage();
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);

    // 創(chuàng)建對(duì)象,進(jìn)行通信
    std::unique_ptr<udpServer> usvr(new udpServer(handleMessage, port));

    usvr->initServer(); // 初始化服務(wù)進(jìn)程
    usvr->start();      // 開始監(jiān)聽
    return 0;
}

【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字&UDP服務(wù)器客戶端的簡單模擬,計(jì)算機(jī)網(wǎng)絡(luò),網(wǎng)絡(luò),計(jì)算機(jī)網(wǎng)絡(luò),udp

2.3 client端的編寫

2.3.1 需要調(diào)用的函數(shù)

頭文件:
	#include <sys/types.h>
   	#include <sys/socket.h>
函數(shù)原型:
   ssize_t sendto(int sockfd, void *buf, size_t len, int falgs, struct sockaddr *dest_addr, socklen_t *addrlen);
函數(shù)描述:
	向指定IP和端口號(hào)的進(jìn)程發(fā)送信息
參數(shù)解釋:
	sockfd:用于發(fā)送的sockfd
	buf:要發(fā)送的數(shù)據(jù)的地址
	len:要發(fā)送的數(shù)據(jù)長度
	flags:發(fā)送的方式,默認(rèn)為0,阻塞發(fā)送
	dest_addr:接收方的IP和端口號(hào)
	addrlen:結(jié)構(gòu)體大小
返回值:
	調(diào)用成功返回接發(fā)送的字節(jié)數(shù),出錯(cuò)返回-1同時(shí)設(shè)置錯(cuò)誤碼

2.3.2 代碼

/*udpCliet.hpp*/
#pragma once
#include <iostream>
#include <string>
#include <string.h>
#include <cerrno>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

namespace Client
{
    enum // 枚舉出錯(cuò)類型
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
    };
    class udpClient
    {
    public:
        udpClient(const std::string &serverIp, const uint16_t &serverPort)
            : _serverIp(serverIp), _serverPort(serverPort), _sockfd(-1), _quit(false) {}
        void initClient()
        {
            // 1.創(chuàng)建套接字
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd == -1)
            {
                std::cerr << "socket error: " << errno << " : " << strerror(errno) << std::endl;
                exit(SOCKET_ERR);
            }
            std::cout << "socket success: " << _sockfd << std::endl;

            // 2. bind
            // a. Client要不要bind   -- 當(dāng)然要
            // b. Client要不要程序員顯示bind   -- 不用,由OS自動(dòng)形成端口進(jìn)行bind。因?yàn)镃lient是在客戶機(jī)上運(yùn)行的,客戶機(jī)上同時(shí)會(huì)有很多其他的Client在跑,不能確定哪些端口已經(jīng)被使用
            // c. OS在什么時(shí)候,如何bind    -- 在Client發(fā)起網(wǎng)絡(luò)連接時(shí)自動(dòng)執(zhí)行bind操作
        }
        void run()
        {
            struct sockaddr_in server;
            memset(&server, 0, sizeof server);
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(_serverIp.c_str());
            server.sin_port = htons(_serverPort);

            std::string message;
            while(!_quit)
            {
                std::cout << "Please Enter# ";
                std::cin >> message;

                sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
            }
        }

    private:
        int _sockfd;           // 套接字
        std::string _serverIp; // 服務(wù)端IP
        uint16_t _serverPort;  // 服務(wù)端端口號(hào)
        bool _quit;            // 客戶端退出標(biāo)志
    };
} // namespace Client
/*udpCliet.cc*/
#include "udpClient.hpp"
#include <string>
#include <memory>

using namespace Client;
static void Usage(std::string proc)
{
    std::cout << "\nUsage:\n\t" << proc << " server_ip server_port\n\n";
}

// ./udpClient server_ip server_port
int main(int argc, char *argv[])
{
    // 解析指令
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);

    std::unique_ptr<udpClient> ucli(new udpClient(serverIp, serverPort));
    ucli->initClient();
    ucli->run();

    return 0;
}

2.3.3 測試

./udpClient 127.0.0.1 18989 # 客戶端啟動(dòng)
./udpServer 18989 # 服務(wù)端啟動(dòng)

【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字&UDP服務(wù)器客戶端的簡單模擬,計(jì)算機(jī)網(wǎng)絡(luò),網(wǎng)絡(luò),計(jì)算機(jī)網(wǎng)絡(luò),udp


本節(jié)完…文章來源地址http://www.zghlxwxcb.cn/news/detail-830987.html

到了這里,關(guān)于【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字&UDP服務(wù)器客戶端的簡單模擬的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 計(jì)算機(jī)網(wǎng)絡(luò)套接字編程實(shí)驗(yàn)-TCP多進(jìn)程并發(fā)服務(wù)器程序與單進(jìn)程客戶端程序(簡單回聲)

    1.實(shí)驗(yàn)系列 ·Linux NAP-Linux網(wǎng)絡(luò)應(yīng)用編程系列 2.實(shí)驗(yàn)?zāi)康?·理解多進(jìn)程(Multiprocess)相關(guān)基本概念,理解父子進(jìn)程之間的關(guān)系與差異,熟練掌握基于fork()的多進(jìn)程編程模式; ·理解僵尸進(jìn)程產(chǎn)生原理,能基于|sigaction()或signal(),使用waitpid()規(guī)避僵尸進(jìn)程產(chǎn)生; ·

    2024年02月12日
    瀏覽(37)
  • 計(jì)算機(jī)網(wǎng)絡(luò)套接字編程實(shí)驗(yàn)-TCP單進(jìn)程循環(huán)服務(wù)器程序與單進(jìn)程客戶端程序(簡單回聲)

    1.實(shí)驗(yàn)系列 ·Linux NAP-Linux網(wǎng)絡(luò)應(yīng)用編程系列 2.實(shí)驗(yàn)?zāi)康?·理解并掌握在程序運(yùn)行時(shí)從命令行讀取數(shù)據(jù)的C語言編程方法; ·理解并掌握基于命令參數(shù)設(shè)置并獲取IP與Port的C語言編程方法; ·理解并掌握套接字地址的數(shù)據(jù)結(jié)構(gòu)定義與地址轉(zhuǎn)換函數(shù)應(yīng)用; ·理解并掌握網(wǎng)絡(luò)字節(jié)序

    2024年02月11日
    瀏覽(43)
  • 計(jì)算機(jī)網(wǎng)絡(luò)--網(wǎng)絡(luò)編程(1)

    計(jì)算機(jī)網(wǎng)絡(luò)--網(wǎng)絡(luò)編程(1)

    簡單認(rèn)識(shí)一下傳輸層中的UDP和TCP: TCP:有鏈接,可靠傳輸,面向字節(jié)流,全雙工 UDP:無連接,不可靠傳輸,面向數(shù)據(jù)報(bào),全雙工 有鏈接類似于打電話,通了就是有鏈接。沒通就一直在等待。 無連接類似于發(fā)短信,只管發(fā),不管到。 可靠傳輸就是保證信息傳輸?shù)目煽啃?。?/p>

    2024年02月11日
    瀏覽(35)
  • 【計(jì)算機(jī)網(wǎng)絡(luò)】4 Socket網(wǎng)絡(luò)編程

    【計(jì)算機(jī)網(wǎng)絡(luò)】4 Socket網(wǎng)絡(luò)編程

    目錄 寫在前面的話 概覽 環(huán)境 URL請(qǐng)求程序: 2. 系統(tǒng)時(shí)間查詢 服務(wù)端 T_TCPServer.py代碼 客戶端 T_TCPClient.py代碼 運(yùn)行效果 3. 網(wǎng)絡(luò)文件傳輸 服務(wù)端 TF_TCPServer.py代碼 運(yùn)行效果(后面加了遠(yuǎn)程功能,效果圖暫時(shí)還在本地) 4. 網(wǎng)絡(luò)聊天室 服務(wù)端 UDPServer.py代碼 客戶端 UDPClient.py代碼 運(yùn)

    2024年02月01日
    瀏覽(34)
  • 【計(jì)算機(jī)網(wǎng)絡(luò)】Socket編程

    【計(jì)算機(jī)網(wǎng)絡(luò)】Socket編程

    IP地址:公網(wǎng)IP,用于唯一標(biāo)識(shí)互聯(lián)網(wǎng)中的一臺(tái)主機(jī) 源IP,目的IP:對(duì)于一個(gè)報(bào)文來講,從哪來,到哪去。 源IP指將數(shù)據(jù)發(fā)送過來的IP地址,目的IP指將數(shù)據(jù)發(fā)送給下一個(gè)設(shè)備的IP地址(mac地址的變化) 意義: 指導(dǎo)一個(gè)報(bào)文該如何進(jìn)行路徑選擇,目的IP是讓我們根據(jù)目標(biāo)進(jìn)行路徑選

    2024年02月08日
    瀏覽(29)
  • 【計(jì)算機(jī)網(wǎng)絡(luò)】socket編程基礎(chǔ)

    【計(jì)算機(jī)網(wǎng)絡(luò)】socket編程基礎(chǔ)

    因特網(wǎng)上的每臺(tái)計(jì)算機(jī)都有一個(gè)唯一的IP地址,如果一臺(tái)主機(jī)上的數(shù)據(jù)要傳輸?shù)搅硪慌_(tái)主機(jī),那么對(duì)端主機(jī)的IP地址就應(yīng)該作為該數(shù)據(jù)傳輸時(shí)的目的IP地址。但僅僅知道目的IP地址是不夠的,當(dāng)對(duì)端主機(jī)收到數(shù)據(jù)后,對(duì)端還需要對(duì)該主機(jī)作出相應(yīng),因此對(duì)端主機(jī)也需要發(fā)送數(shù)據(jù)

    2024年02月15日
    瀏覽(37)
  • 計(jì)算機(jī)網(wǎng)絡(luò)技術(shù)與JAVA網(wǎng)絡(luò)編程URL編程-----JAVA入門基礎(chǔ)教程-----計(jì)算機(jī)網(wǎng)絡(luò)經(jīng)典

    import org.junit.jupiter.api.Test; import java.io.*; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; public class URLTest { public static void main(String[] args) { //URL:統(tǒng)一資源定位符(種子),一個(gè)URL就定位著互聯(lián)網(wǎng)上某個(gè)資源的地址 //http:應(yīng)用層協(xié)議,IP地址,端口號(hào),資源地址,參數(shù)

    2024年02月15日
    瀏覽(99)
  • 計(jì)算機(jī)網(wǎng)絡(luò)技術(shù)與JAVA網(wǎng)絡(luò)編程UDP編程-----JAVA入門基礎(chǔ)教程-----計(jì)算機(jī)網(wǎng)絡(luò)經(jīng)典

    import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.*; public class UDP { public static void main(String[] args) { DatagramSocket datagramSocket = null; try { datagramSocket = new DatagramSocket(); InetAddress inetAddress = InetAddress.getByName(\\\"127.0.0.1\\\"); int port = 9090; byte[] byte

    2024年02月15日
    瀏覽(36)
  • 【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程接口 Socket API 解讀(3)

    ?????????Socket 是網(wǎng)絡(luò)協(xié)議棧暴露給編程人員的 API,相比復(fù)雜的計(jì)算機(jī)網(wǎng)絡(luò)協(xié)議,API 對(duì)關(guān)鍵操作和配置數(shù)據(jù)進(jìn)行了抽象,簡化了程序編程。 ? ? ? ? 本文講述的 socket 內(nèi)容源自 Linux man。本文主要對(duì)各 API 進(jìn)行詳細(xì)介紹,從而更好的理解 socket 編程。 poll()????????? ?遵

    2024年02月09日
    瀏覽(26)
  • 【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程接口 Socket API 解讀(5)

    ?????????Socket 是網(wǎng)絡(luò)協(xié)議棧暴露給編程人員的 API,相比復(fù)雜的計(jì)算機(jī)網(wǎng)絡(luò)協(xié)議,API 對(duì)關(guān)鍵操作和配置數(shù)據(jù)進(jìn)行了抽象,簡化了程序編程。 ? ? ? ? 本文講述的 socket 內(nèi)容源自 Linux man。本文主要對(duì)各 API 進(jìn)行詳細(xì)介紹,從而更好的理解 socket 編程。 connect()?????????

    2024年02月08日
    瀏覽(34)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包