一、IP地址和端口

套接字接口可以用于计算机间通信。目前计算机间使用套接字通讯需要保证处于同一网段。

为了查看是否处于同一网段,我们可以使用IP地址判断。

IP地址是计算机在网络中的唯一标识。IP地址本质是个整数,它与网卡的物理地址(MAC地址)绑定。MAC地址在网卡出厂时都确保唯一,不需要我们关心。

IP地址有IPv4和IPv6之分,IPv4是32位整数,IPv6是128位整数。现在使用的一般是IPv4。

为了便于记忆,IPv4地址的每个字节转换为一个整数(8位整数,0到255),各个整数之间使用“.”隔开,这种方式叫点分十进制。如192.168.7.5。

人类使用的是点分十进制,计算机底层存储的是整数格式(十六进制)。两种表示方法在编程时需要互相转换

当我们上网时输入网址如baidu.com,这个baidu.com域名是IP地址的助记符。域名需要转换成IP地址才能找到计算机,这个工作由域名解析服务器完成。

在Linux中,我们可以使用ifconfig查看当前计算机的IP地址

上图中IPv4为127.0.0.1的lo为本地回环,简单点说就是用于本机通讯。

可以看到我的系统的IPv4地址为7.7.7.5,若有和我处于一个网络电脑的IPv4地址为7.7.7.3。我们需要使用位与判断这两个IP地址是否处于同一网段。

首先把7.7.7.5位与Mask(255.0.0.0),得到7.0.0.0;

之后把7.7.7.3位与Mask(255.0.0.0),得到7.0.0.0。

可以发现结果一致,表示两IP处于同一网段。

但是IP地址只能定位计算机,不能定位计算机中的进程。为解决此问题,系统提供了端口。

端口用于计算机对外管理进程。因此网络编程需要提供IP地址和端口号。

端口的本质是一个unsigned short(0到65535),代表了计算机中的每一个进程。这些进程中,有些端口已经被占用,编程中需要使用未被占用的端口。

0 到 1023:系统预先占用部分,最好别用

1024 到 48000:可以使用,但有个别端口被其它程序占用

48000 到 65535:系统可能会随时使用某一个,不稳定

二、字节顺序

有了IP和端口后,传输的源头和目的地就有了,此时还需要数据和数据格式。

在网络中,数据格式并不是确定的,因此我们在发送或接受数据时,需要把本机格式转换网络格式或把网络格式还原本机格式。

IP本机格式转换网络格式使用函数为inet_addr(),如:inet_addr("7.7.7.5");

IP网络格式转换本机格式使用函数为inet_ntoa(),如:inet_ntoa(sockaddr.sin_addr);

端口本机格式转换网络格式使用函数为htons(),如:htons(8888);

三、套接字的编程步骤

网络编程需要考率两个方面:服务器端和客户端。

服务器端的编程步骤:

1. 调用socket()函数,创建一个socket描述符,它和fd使用方法一致

2. 配置struct sockaddr_in或struct sockaddr_un,准备进行数据交互

3. 调用bind()函数,绑定通信地址和socket描述符

4. 调用read()、write(),读写socket描述符

5. 调用close()函数,关闭socket描述符

客户端的编程步骤:

除了服务器端步骤3的bind()换成connect()以外,其它步骤和服务器端一样,并且connect()和bind()用法完全一样。

套接字类函数和结构体定义如下:

1. 创建socket描述符

#include <sys/types.h>
#include <sys/socket.h> /* 创建socket描述符 */
int socket(int domain, int type, int protocol);

函数参数以及返回值:

domain:选择协议,有以下宏:

AF_UNIX/AF_LOCAL/AF_FILE:本地通信

AF_INET:网络通信IPv4

AF_INET6:网络通信IPv6

type:通信类型,有以下宏:

SOCK_STREAM:数据流,用于TCP(有数据回传机制,可以判断是否发送成功)

SOCK_DGRAM:数据报,用于UDP(没有数据回传机制)

返回值:成功返回socket描述符;出错返回-1。


2. 配置struct sockaddr_in或struct sockaddr_un

其实通信使用的结构体是sockaddr。它是sockaddr_in和sockaddr_un的一般化,sockaddr_in和sockaddr_un做参数时必须转化为sockaddr。

在代码中,sockaddr_in负责网络通信;sockaddr_un负责本地通信。

其中sockaddr_un结构体定义如下:

#include <sys/un.h>

struct sockaddr_un
{
int sun_family; // 用于指定协议,和socket()的第一个参数保持一致
char sun_path[]; // 存socket文件名(做交互媒介)
// 注意,数组不能直接用=赋值
}; /* 示例 */
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "a.sock"); // 系统会自动创建a.sock文件

sockaddr_in结构体定义如下:

#include <netinet/in.h>

struct sockaddr_in
{
int sin_family; // 用于指定协议,和socket()的第一个参数保持一致
short sin_port; // 端口号
struct in_addr sin_addr; // 存储IP地址的结构
}; /* 示例 */
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
addr.sin_addr.s_addr = inet_addr("7.7.7.5");

3. 绑定socket描述符和struct sockaddr

#include <sys/types.h>
#include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); /* 示例 */
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));

四、TCP通信

由于TCP会进行回传工作等,因此TCP在前三步的基础上,需要监听客户端回应并等待客户端连接。

服务器端的编程步骤:

1. 调用socket()函数,创建一个socket描述符,它和fd使用方法一致

2. 配置struct sockaddr_in或struct sockaddr_un,准备进行数据交互

3. 调用bind()函数,绑定通信地址和socket描述符

4. 调用listen()函数,监听客户端

5. 调用accept()函数,等待客户端连接。accept()会返回客户端的socket描述符,用于读写交互

6. 调用read()、write(),读写socket描述符

7. 调用close()函数,关闭socket描述符

客户端的编程步骤:

与之前一致

listen()函数定义如下:

#include <sys/types.h>
#include <sys/socket.h> int listen(int sockfd, int backlog); /* 示例 */
listen(sockfd, ); // 监听100个客户端

accept()函数定义如下:

#include <sys/types.h>
#include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); /* 示例 */
/* 客户端fd 返回的客户端addr */
clientfd = accept(sockfd, (struct sockaddr*)&client, &clientlen);

服务器示例代码如下:

 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> int main()
{
int id = socket(AF_INET, SOCK_STREAM, );
if (id == -)
perror("socket"), exit(-); struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); /* 防止SIGINT后一段时间端口占用 */
int reuse = ;
setsockopt(id, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); int res = bind(id, (struct sockaddr*)&addr, sizeof(addr));
if (res == -)
perror("bind"), exit(-);
printf("绑定完成!\n"); listen(id, ); int client;
struct sockaddr_in from;
socklen_t len = sizeof(from); client = accept(id, (struct sockaddr*)&from, &len);
if (client == -)
perror("accept"), exit(-);
printf("%s连接了\n", inet_ntoa(from.sin_addr)); char buf[];
read(client, buf, sizeof(buf));
printf("接收数据为:%s\n", buf); close(client); return ;
}

客户端示例代码如下:

 #include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h> int main()
{
int fd = socket(AF_INET, SOCK_STREAM, );
if (fd == -)
perror("socket"), exit(-); struct sockaddr_in sock;
sock.sin_family = AF_INET;
sock.sin_port = htons();
sock.sin_addr.s_addr = inet_addr("127.0.0.1"); int res = connect(fd, (struct sockaddr *)&sock, sizeof(sock));
if (res == -)
perror("connect"),exit(-);
printf("连接成功\n"); char buf[]; strcpy(buf, "Hello World"); res = write(fd, buf, strlen(buf));
printf("发送成功\n"); close(fd); return ;
}

测试时首先启动服务器程序,之后启动客户端。

五、UDP通信

服务器端的编程步骤:

1. 调用socket()函数,创建一个socket描述符,它和fd使用方法一致

2. 配置struct sockaddr_in或struct sockaddr_un,准备进行数据交互

3. 调用bind()函数,绑定通信地址和socket描述符

4. 调用read()或recvfrom()读socket描述符,调用sendto()写socket描述符

5. 调用close()函数,关闭socket描述符

客户端的编程步骤:

不需要之前的connect()函数;调用recvfrom()、sendto(),读写socket描述符

read()和recvfrom()的区别在于:

read()只能读数据,不能读发送方的通信地址,而recvfrom()两者皆可

recvfrom()和sendto()函数定义如下:

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);

其中,

参数flags用于指定是否阻塞,如不需要阻塞等待可设置为MSG_DONTWAIT,阻塞等待可设置为0。

参数src_addr表示发送数据的地址;参数dest_addr表示接收数据的地址。

需要注意的是recvfrom()函数的最后一个参数采用的是指针传递,而sendto采用的值传递。

服务器示例代码如下:

 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> int main()
{
int id = socket(AF_INET, SOCK_DGRAM, );
if (id == -)
perror("socket"), exit(-); struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); /* 防止SIGINT后一段时间端口占用 */
int reuse = ;
setsockopt(id, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); int res = bind(id, (struct sockaddr*)&addr, sizeof(addr));
if (res == -)
perror("bind"), exit(-);
printf("绑定完成!\n"); int client;
struct sockaddr_in from;
socklen_t len = sizeof(from); char buf[];
recvfrom(id, buf, sizeof(buf), , (struct sockaddr*)&from, &len);
printf("%s连接了,接收数据为:%s\n", inet_ntoa(from.sin_addr), buf); close(client); return ;
}

客户端示例代码如下:

 #include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h> int main()
{
int fd = socket(AF_INET, SOCK_DGRAM, );
if (fd == -)
perror("socket"), exit(-); struct sockaddr_in sock;
sock.sin_family = AF_INET;
sock.sin_port = htons();
sock.sin_addr.s_addr = inet_addr("127.0.0.1"); char buf[]; strcpy(buf, "Hello World"); int res = sendto(fd, buf, strlen(buf), , (struct sockaddr *)&sock, sizeof(sock));
printf("发送成功\n"); close(fd); return ;
}

本章给出的示例代码过于简单,以后我会融合信号、多线程和线程同步知识,编写一个服务器支持多客户端示例。

第十六章:网络IPC 套接字的更多相关文章

  1. apue学习笔记(第十六章 网络IPC:套接字)

    本章将考察不同计算机(通过网络连接)上的进程相互通信的机制:网络进程间通信. 套接字描述符 正如使用文件描述符访问文件,应用程序用套接字描述符访问套接字. 许多处理文件描述符函数(如read和writ ...

  2. Unix环境高级编程(十七)网络IPC套接字

    通过网络套接字可以使得不同计算机上运行的进程相互通信. 1.创建套接字 #include <sys/socket.h> Int socket( int domain, int type, ...

  3. SpringBoot | 第二十六章:邮件发送

    前言 讲解了日志相关的知识点后.今天来点相对简单的,一般上,我们在开发一些注册功能.发送验证码或者订单服务时,都会通过短信或者邮件的方式通知消费者,注册或者订单的相关信息.而且基本上邮件的内容都是模版 ...

  4. 鸟哥的linux私房菜——第十六章学习(程序管理与 SELinux 初探)

    第十六章.程序管理与 SE Linux 初探 在 Linux 系统当中:"触发任何一个事件时,系统都会将他定义成为一个程序,并且给予这个程序一个 ID ,称为 PID,同时依据启发这个程序的 ...

  5. 《Linux命令行与shell脚本编程大全》 第十六章 学习笔记

    第十六章:创建函数 基本的脚本函数 创建函数 1.用function关键字,后面跟函数名 function name { commands } 2.函数名后面跟空圆括号,标明正在定义一个函数 name ...

  6. Gradle 1.12 翻译——第十六章. 使用文件

    有关其它已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或訪问:http://gradledoc.qiniudn.com ...

  7. 第十六章——处理锁、阻塞和死锁(3)——使用SQLServer Profiler侦测死锁

    原文:第十六章--处理锁.阻塞和死锁(3)--使用SQLServer Profiler侦测死锁 前言: 作为DBA,可能经常会遇到有同事或者客户反映经常发生死锁,影响了系统的使用.此时,你需要尽快侦测 ...

  8. CSS3秘笈复习:十三章&十四章&十五章&十六章&十七章

    第十三章 1.在使用浮动时,源代码的顺序非常重要.浮动元素的HTML必须处在要包围它的元素的HTML之前. 2.清楚浮动: (1).在外围div的底部添加一个清除元素:clear属性可以防止元素包围浮 ...

  9. Gradle 1.12用户指南翻译——第二十六章. War 插件

    其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...

随机推荐

  1. jQuery Cookie (内附 上百行的中文使用手册,与 所有的注释中文翻译)

    jQuery Cookie (内附 上百行的中文使用手册,与 所有的注释中文翻译) 博主亲自翻译. 大家多多捧场. 更多资源请点击"查看TA 的资源" .全场通通 2积分. htt ...

  2. BZOJ1211树的计数

    裸的prufer结论. 给个小链接prufer序列 ,里面有一个性质4就是本题答案,严谨证明可以上网找一找,如果从多组组合角度理解也可以. 剩下的就是特判,n==1时,du==0,1个,du!=0,废 ...

  3. mac使用xposed超详细入门级教程Android Studio-20190930

    工具 这里我使用的工具是Android Studio3.4.1,电脑环境mac os mojave 10.14.6(这个应该问题不大) 创建项目 1.打开Android Studio,看到这个界面,并 ...

  4. java——反射

    http://www.cnblogs.com/hxsyl/archive/2013/03/23/2977593.html

  5. Radix Heap ---Dijkstra算法的优化 BY Gremount

    Radix Heap 算法是在Dijkstra的Dial实现的基础上,通过减少对桶的使用,来优化算法的时间复杂度: Dial 时间复杂度是O(m+nC)     -------C是最长的链路 Radi ...

  6. TynSerial基本数据类型序列(还原)

    TynSerial基本数据类型序列(还原) procedure TForm1.ToolButton17Click(Sender: TObject); var serial: TynSerial; be ...

  7. chrome dev

    chrome://plugins 为什么无法打开? Chrome插件问答 2018-03-02 13:34     最后又很多网友在我们 chrome插件 网反应说chrome://plugins 无 ...

  8. raid卷性能测试

    #RAID卷 独立磁盘冗余阵列RAID是一种把多块独立的硬盘(物理硬盘)按不同的方式组合起来形成一个硬盘组(逻辑硬盘),从而提供比单个硬盘更高的存储性能和提供数据备份技术.组成磁盘阵列的不同方式成为R ...

  9. Linux内存使用情况以及内存泄露情况

    1. 内存使用情况分析 http://www.360doc.com/content/15/1118/13/17283_514054063.shtml https://www.linuxidc.com/ ...

  10. 相关 Excel 开源类库性能对比

    性能数据 · Excelize 中文文档https://xuri.me/excelize/zh-hans/performance.html Golang library for reading and ...