C/C++网络编程一:概述

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/chenchukun/article/details/78991215

最近在重新看UNIX网络编程第一卷,上大学的时候粗略的过了一遍,没怎么做记录,所以这次想写几篇博客记录一下学习过程,希望能坚持下来。

关于网络编程

这里指的是Linux环境下使用系统提供的socket API进行的网络编程开发,在实际工作中基本不会使用原始的socket API来开发,都是使用现成的库或框架,但是理解socket API对正确的使用这些库或框架,编写正确和高效的程序是很有帮助的。
使用socket API进行网路编程最简单的就是调用socket、bind、listen、accept、read、write这几个系统函数,说简单也简单,但是想写真正可靠的程序却不容易,各种边界添加和特殊情况都需要去仔细的考虑。


传输层协议TCP和UDP

这里只讨论使用TCP协议的网络编程,由于UDP的不可靠性,现在使用的越来越少,但是为了和TCP做对比,这里还是稍微提一下概念。

用户数据报协议(UDP)

UDP是一个简单的、无连接、不可靠的传输层协议,应用进程往一个UDP套接字写入一个消息,该消息随后被封装到一个UDP数据报,该UDP数据报进而被封装到一个IP数据报,然后发完目的地。UDP不保证UDP数据报会到达最终目的地,不保证各个数据报达到目的地的先后顺序,也不保证每个数据报只到达一次。

​ 每个UDP数据报后有一个长度,在数据报正确到达目的地后,长度会随数据传递给应用程序,所以通常情况下在应用程序看来,UDP每次接收到的都是与发送的大小一致的数据包,而TCP是一个字节流协议,传输的数据是没有边界的。

​ 下图显示的是UDP的首部,从中可以看出,UDP其实就是在IP的基础上多提供的了一个端口,可以实现通过端口来将数据传送给特定的进程。



传输控制协议(TCP)

TCP提供了面向连接的,靠性的服务,与UDP相比,它具有确认、超时重传、流量控制、拥塞控制等机制。

在两端进行通信前必须先建立连接,当TCP向另一端发送数据时,将启动一个定时器,它要求对端返回一个确认,如果在定时器超时后还没有收到确认,TCP就自动重传数据并等待更长时间。在数次重传失败后TCP才放弃。

​ 当TCP接收到另一端TCP发来的数据后,将发送一个确认,这个确认不一定是立即发送的,通常将在推迟一段时候后发送(延时确认机制)。

​ TCP将保持她首部和数据的校验和,这是一个端到端的校验和,目的是检验数据在传输过程中的任何变化,如果收到端的校验和有差错,TCP将丢弃这个报文段并发送确认,等待发送端超时重传。

​ TCP并不保证数据一定会被对端接收,因为这是不可能做到的,如果有可能,TCP就将数据递送到对端,否则就通知用户。因此,TCP也不能被描述成是100%可靠的协议,它提供的是数据的可靠递送货故障的可靠通知。

​ TCP含有用于动态估算客户和服务器之间的往返时间(round-trip time, RTT)的算法,以便它知道等待一个确认需要多长时间。

​ TCP通过给其中的每个字节关联一个序列号对所发送的数据进行排序。若接收端接收到的数据非顺序到达,接收端TCP将先根据它们的序列化重新排序,再把结果数据传递给接收应用。若接收到了重复数据,它将根据序列号判定数据是重复的,从而丢弃重复数据。

​ TCP总是告知对端在任何时刻它一次能够从对端接收多少字节的数据,这称为通知窗口,在任何时刻,该窗口指出接收缓冲区当前可用的空间量,从而确保发送端发送的数据不会使接收缓冲区溢出。

下图为TCP的首部信息,与UDP相比,多了很多的字段信息,这些字段信息对于保证可靠性都有着各自的作用,对于各自的用途这里先不多说。


TCP网络编程一般步骤

这里使用一个简单的时间服务器和客户端来演示TCP网络编程的一般步骤。服务端例子

#include <netinet/in.h>
#include <sys/socket.h>
#include <cstring>
#include <ctime>
#include <unistd.h>
#include <iostream>
#include <arpa/inet.h>

using namespace std;

int main()
{
    int lisFd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    bzero(&addr, sizeof(addr));
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6180);

    bind(lisFd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
    listen(lisFd, 100);
    char buff[64];
    while (true) {
        struct sockaddr_in remoteAddr;
        socklen_t len;
        int sock = accept(lisFd, reinterpret_cast<sockaddr*>(&remoteAddr), &len);
        if (sock >= 0) {
            time_t now = time(NULL);
            ctime_r(&now, buff);
            write(sock, buff, strlen(buff));
            close(sock);
        }
    }
    return 0;
}
TCP服务端编程的一般步骤为:

  1. 调用socket函数,创建一个套接字描述符。
  2. 创建网络地址结构,指定要监听的IP和端口号。
  3. 调用bind函数,将套接字描述符与网络地址结构绑定。
  4. 调用listen函数,将套接字描述符转为监听套接字,表示该描述符是用于从指定地址和端口接收连接的。
  5. 调用accept函数来获取链接。
  6. 得到连接后使用read和write函数完描述符里读写数据。
  7. 完成后调用close关闭描述符。

客户端例子

#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
#include <arpa/inet.h>
#include <iostream>
#include <unistd.h>
using namespace std;

int main()
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6180);
    inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
    if (connect(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
        cerr << "connect error " << endl;
        exit(-1);
    }
    char buff[64];
    int offset = 0;
    int len;
    while ((len = read(fd, buff + offset, sizeof(buff)-offset)) >= 0) {
        if (len == 0) {
            if (errno == EINTR) {
                continue;
            }
            break;
        }
        offset += len;
        buff[offset] = 0;
    }
    if (len < 0) {
        cerr << "read error" << endl;
        exit(-1);
    }
    cout << buff << endl;
    return 0;
}	
TCP客户端端编程的一般步骤为:

  1. 调用socket函数,创建一个套接字描述符。
  2. 创建网络地址结构,指定要连接的服务端的IP和端口号。
  3. 调用connect函数连接服务端。
  4. 连接成功后调用read、write函数读写数据
  5. 完成后调用close关闭描述符。
展开阅读全文

没有更多推荐了,返回首页