Unix高级编程:网络基础、基于TCP以及UDP的编程模型、TCP高并发编程模型

"ipcs" //man ipcs
功能:显示共享内存段、信号量数组、消息队列
命令行:ipcs


"ipcrm" 
ipcrm [ -M key | -m id | -Q key | -q id | -S key | -s id ] ...
功能:移除消息队列
命令行:ipcrm -q id


"ipcmk"
ipcmk [-M size] [-S nsems] [-Q] [-p mode]
功能:创建一个不同的ipc资源


一、网络基础
1. 客户端和服务器端的架构
"client" 客户端
"server" 服务器端


2. 什么是协议? "协议就是规则"


3. 网络协议采用的是 "TCP/IP协议族" (4(2~5)|5(1~5) 层)
TCP/IP协议的分层:/** 软件分层 **/
5>"应用层":Telnet、FTP和e-mail等
4>"传输层":TCP和UDP
3>"网络层":IP、ICMP和IGMP
2>"链路层":设备驱动程序及接口卡
1>"物理层":————第5层,规定了电气协议


4. 物理层规范了电气规则(电气协议)


补充:
1)"网络架构"
C/S架构 - 客户端
B/S架构 - 浏览器
"B/S是C/S的一种"


2)网线:跑的是电流
模电 / "数电"(高电压是1,低电压是0) 


3)"以太网数据帧的分用过程图 - 英文注解"
ARP 地址解析协议,把ipv4地址映射到硬件地址
RARP 反向地址解析协议,把硬件地址映射到ipv4地址
IP 网络协议
TCP 传输控制协议
UDP 用户数据报协议
ICMP Internet报文控制协议
IGMP 组管理协议
ICMPv6 网际控制消息协议版本6,综合了ICMP/IGMP/ARP的功能
BPF BSD分组过滤器,为应用程序提供访问数据链路层的接口,由源自BSD的系统内核提供
DLPI 数据链路提供者接口,为应用程序提供访问数据链路层的接口,由源自SVR4的系统内核提供


5. 客户端和服务器端需要有一个传输的基本单元,需要知道这个数据单元的起始标记和结束标记。这个单元就是"帧"。(以太网对应的是"以太网帧")
比如:农村家里接自来水,用水桶,接满一桶放到水缸。
/** 自来水 - 网络数据包;一水桶的水 - 帧;水缸 - 内存。 **/


6. 实现客户端和服务器端的通信,需要知道"对方的地址"和"端口号"。
不同的网络应用会使用不同的端口号。使用IP地址定位主机,再使用端口号定位定位运行在这台主机上的一个具体的网络应用。
0~65535 其中习惯上 0~5000 为系统所用,一般应用选择5000以上端口。
21 端口用于ftp服务,23 端口用于telnet服务,80 端口用于www服务等。
(IP地址和端口号:socket pair "通讯对")


7. 地址:逻辑地址/网络地址("IP地址")、物理地址("MAC地址")
每一块网卡都有自己的身份证号,即物理MAC地址(目的地址 - 6 个字节)
网络通信中最终认识的就是此MAC地址。
查看计算机的网络地址/物理地址:"sudo ifconfig"
网络/逻辑地址——inet地址,物理地址——硬件地址,"client一对一绑定"。
机器需要将逻辑地址和物理地址做绑定。


8. ip地址分为2种:ipv4("4个字节的32位地址")
 ipv6("16个字节的128位地址")
ip地址包括两部分内容,"网络号和主机号"。
"A类":0~127.0~255.0~255.0~255 以0为首的8位网络地址+24位本地地址
0.0.0.0  "主机号全0不能使用",主机号全0代表的是网络号。
0.255.255.255 "主机号全1不能用",这代表的是该网络的广播地址。
127.0.0.0  127.255.255.255
"B类":128~191.0~255.0~255.0~255 以10为首的16位网络地址+16位本地
"C类":192~223.0~255.0~255.0~255 以110为首的24位网络地址+8位本地
"D类":以 1110 为首的32位多播地址(28位多播组号)
"E类":以 11110 为首的32位多播地址(27位留后待用)
将ip地址分为私有ip和共有ip。


9. 子网掩码:有的时候需要将一个大的网络划分为更小的几个网络,这时候需要到子网掩码的技术。
将ip地址和子网掩码做 "与&" 操作,就可以得到这个ip属于哪个子网段。
格式:"IP地址 & 子网掩码 == 子网地址"
举例1:ip(192.168.1.56) & 子网掩码(255.255.255.0) = 192.168.1.0
说明:ip为192.168.1.56的电脑属于192.168.1.0网段的一台机器。
举例2:ip(192.168.129/24, 前面24位全1,即子网掩码255.255.255.0)
  ip(192.168.129/25, 子网掩码255.255.255.128)
192.168.1.129/25 & 255.255.255.128 == 192.168.1.128网段的局域网
192.168.1.123/25 & 255.255.255.128 == 192.168.1.0网段的局域网
以上两个局域网的主机号只有7位了,127-2(全0和全1) = 125 台机器。


10. 三种网络设备:
"集线器"hub,电流的放大和分流,属于电气层/一层交换。工作在物理层。
单个电脑占用网线数据时,其他电脑不可通过网线访问,也不可相互访问。
"交换机",交换的是网帧,帧属于链路层,属于二层交换。
单个电脑占用网线数据时,其他局域网内电脑互相访问不影响。
"路由器",交换的是ip报文,报文属于网络层,所以是三层交换。
实现了无限制访问。


11. 局域网内数据包的传输
从这台例如192.168.1.11的机器传输数据包给192.168.1.12的流程步骤:
第一步:
先判断192.168.1.12的网段和192.168.1.11是否相同,都与255.255.255.0做与&运算,均为192.168.1.0网段,说明是局域网内的传输。所以路由器不会将该数据包路由到外网。
第二步:
查看192.168.1.11机器上的arp表,如果arp表里面有192.168.1.12这条ip数据,那么将这条数据的对应的MAC地址添加到以太网帧的目的地址,这样数据包就被传送过去了,如果arp表中没有这条数据,192.168.1.11这台机器会发送arp请求广播,这时候192.168.1.12收到广播信息,查看与自己的ip地址是否一致。如果不是,不做任何反应;如果是,那就将自己的MAC地址通过arp应答回送给192.168.1.11,这时候192.168.1.11将帧的目的地址设置为该MAC地址,并且在arp表中添加该条记录。以此实现局域网内的数据包传输过程。


路由表 "sudo route -v" //查看路由表
arp表 "sudo arp -a" //查看arp表,ping后可查看对应ip的机器
"ping ip地址" //检测局域网是否连通


12. 三次握手


13. 四次分手


二、基于TCP的编程模型
TCP是面向连接的。客户端和服务器端通讯需要建立连接。
1. 基于TCP的"服务器端编程模型"
<1> 创建socket通讯端(socket) -> 初始化服务器(sockaddr_in成员)
<2> 将通讯端和服务器ip地址和端口号绑定(bind)
<3> 监听通讯端(listen)
<4> 等待客户端连接的到来,返回一个连接描述符(accept)
<5> 从连接描述符中读写数据(read/write)
<6> 对数据进行加工
<7> 关闭socket通讯端-连接描述符(close(参数);)
/** 整体是框架,用户自定的地方主要是<5><6>对数据的读写和处理 **/


2. 基于TCP的"客户端编程模型"
<1> 创建socket通讯端(socket) -> 初始化服务器(sockaddr_in成员)
<2> (inet_pton转换)-> 使用socket通讯端连接服务器
<3> 连接成功,通过socket通讯端向服务器发送数据,或从服务器获取数据
<4> 处理数据
<5> 关闭socket通讯端-连接描述符
/** 整体是框架,用户自定的地方主要是<4>对数据的读写和处理 **/


【服务器端编程模型】
/** 1.<1> 创建socket通讯端 **/
"socket"(2)
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建一个通讯端点(返回一个文件描述符)
参数:
"domain"
AF_INET 用于ipv4的通讯
AF_INET6 用于ipv6的通讯
"type"
SOCK_STREAM 传输层选用的是TCP协议,面向数据流
SOCK_DGRAM 传输层选用的是UDP协议,面向数据包
"protocol" 0
返回值:
成功 - 返回新的文件描述符
失败 - 返回 -1,errno被设置


/** 1.<2> 将通讯端和服务器的ip地址和端口号绑定 **/
"bind"(2)
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
功能:将一个名字绑定到一个socket(第一个参数绑定到第二个参数)
参数:
"sockfd" socket(2)的返回值
"addr" socket pair通讯对,通用协议族 //ip地址和端口号构成的一个结构体
"addrlen" addr的长度
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置


补充:"man in.h"头文件下包含以下3个结构体:
"<netinet/in.h>"
#include <netinet/in.h>
struct sockaddr 通用协议族结构体
struct sockaddr_in ipv4协议族的结构体
struct sockaddr_in6 ipv6协议族的结构体
struct sockaddr {
   sa_family_t sa_family;
   char        sa_data[14];
};
struct sockaddr_in {
sa_family_t sin_family; /* 取值:AF_INET */
in_port_t sin_port; /* Port number:5000以下留给系统*/
struct in_addrsin_addr;/* IP address */
};
struct in_addr {
in_addr_t s_addr;/** INADDR_ANY 表示任意IP均可与服务器通信 **/
}


需要将ip地址的"点分十进制(ddd.ddd.ddd.ddd)"的字符串和无符号整形两种类型互相转换。
/** 点分十进制字符串 -> 网络字节序(无符号整数) **/ 转换
"inet_pton"(3)
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
功能:将ipv4或ipv6的字符串形势的ip地址转换为struct in_addr二元形式
参数:
"af" 
AF_INET IPv4网络
AF_INET6 IPv6网络
"src" 点分十进制的字符串格式
"dst" struct in_addr类型的内容
返回值:
成功 - 返回 1
失败 - 返回 0,代表src是无效地址
失败 - 返回 -1,errno被设置


/** 网络字节序(无符号整数) -> 点分十进制字符串 **/
"inet_ntop"(3)
#include <arpa/inet.h>
const char *inet_ntop(int af,const void *src,char *dst,socklen_t size);
功能:将ipv4或ipv6的地址从binary(二进制)到text(字符串)
参数:
"af" 
AF_INET IPv4网络
AF_INET6 IPv6网络
"src" struct in_addr类型的内容
"dst" 指定的字符串空间
"size" 拷贝到字符串的大小
返回值:
成功 - 返回字符串的首地址
失败 - 返回 NULL,errno被设置


"inet_ntoa"(3) //扩充
"inet_aton"(3) //扩充


一般情况下计算机配置的是小端,但是网络中使用的是大端。不管计算机使用的是大端还是小端,到"网络中必须使用大端"。
"htonl"(3)系列函数 /** 大小端的字节顺序byte order互相转换 **/
#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);
"h" 开头的:host主机字节序
"n" 开头的:net网络字节序
"l" 结尾的:long (长整型 32 位) - 可用于IP
"s" 结尾的:short (短整型 16 位) - 可用于端口


/** 1.<3> 监听通讯端 **/
"listen"(2)
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:监听socket连接
参数:
"sockfd" socket(2)返回值(type参数需指定SOCK_STREAM | SOCK_SEQPACKET)
"backlog" 允许的最大的未决连接数
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置


重点小结:
1、TCP/IP协议的分层(4层或5层,5层包含物理层,7层是模型非实际分层)
2、子网掩码(划分局域网的网段)
3、IP地址的分类
4、三次握手


/** 1.<4> 等待客户端连接的到来,返回一个连接描述符 **/
"accept"(2)
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:创建一个新的连接socket(监听到连接)
参数:
"sockfd" socket(2)的返回值
"addr" 获取客户端的socket pairs(通讯对:ip地址和端口号)
  如果addr为 NULL,addrlen需要置为 NULL
"addrlen" addr结构体变量的长度
返回值:
成功 - 返回 一个新的非负整数文件描述符
失败 - 返回 -1,errno被设置


网络编程


"server"
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>


typedef struct sockaddr SA;
typedef struct sockaddr_in SA4;
int main (void)
{
	SA4 serv, cli; // 定义服务器地址变量,客户端地址变量
	int s_fd, conn_fd;
	int ret = 0;
	char buf[1024] = {0}, buf1[1024] = {0};
	char ip[128] = {0}; // 存放ip
	int port; // 存放端口
	/* 1. 创建socket通讯端 */
	s_fd = socket (AF_INET, SOCK_STREAM, 0);
	if (-1 == s_fd) {
		perror ("socket");
		return 1;
	}
	/* 2. 初始化服务器信息 */
	serv.sin_family = AF_INET; // ipv4
	serv.sin_port = htons (7778); // port
	serv.sin_addr.s_addr = htonl (INADDR_ANY); // 32bit long
	/* 3. 将服务器ip地址和端口号与s_fd绑定 */
	ret = bind (s_fd, (SA *)&serv, sizeof (serv));
	if (-1 == ret) {
		perror ("bind");
		return 2;
	}
	/* 4. 监听s_fd */
	listen (s_fd, 10); // 5个设备同时
	/* 5. 阻塞等待客户端的连接 */
	while (1) {
		/* 将客户端的通讯对保存在了cli指向的地址里 */
		int cli_len = sizeof (cli);
		conn_fd = accept (s_fd, (SA*)&cli, &cli_len);
		/* 将二进制ip转换为字符串ip地址打印 */
		char *p = (char*)inet_ntop (AF_INET, &cli.sin_addr, ip, 128);
		printf ("客户端ip:%s\n", p);
		printf ("端口号:%d\n", ntohs (cli.sin_port));
		if (-1 == conn_fd) {
			perror ("accept");
			return 3;
		}
		/* 6. 读取客户端发送过来的信息 */
		int r = read (conn_fd, buf, sizeof (buf)-1);
		/* 7. 数据处理/显示 */
		write (1, buf, r);
		// write (1, "\n", 2);
		memset (buf, 0, sizeof (buf));
		/* 8. 给客户端响应 */
		fgets (buf1, sizeof (buf1), stdin);
		write (conn_fd, buf1, sizeof (buf1));
		memset (buf1, 0, sizeof (buf1));
	}
	/* 9. 连接结束,关闭连接 */
	close (conn_fd);
	return 0;
}

【客户端编程模型】
/** 1.<1> 创建socket通讯端 **/
同服务器端函数。
/** 1.<2> 使用socket通讯端连接服务器 **/
同服务器端函数。
/** 1.<3> 连接成功,通过socket通讯端向服务器发送数据,或从服务器获取数据 **/
"connect"(2)
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, \
socklen_t addrlen);
功能:将sockfd连接到addr指定的地址空间
参数:
"sockfd" socket(2)的返回值
"addr" 要连接到的目的地址(struct sockaddr通讯对类型的)
"addrlen" addr的长度
返回值:
成功 - 返回 0 (连接或绑定成功)
失败 - 返回 -1,errno被设置

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


typedef struct sockaddr SA;
typedef struct sockaddr_in SA4;
int main (int argc, char *argv[])
{
	SA4 server;
	int s_fd;
	int ret;
	char buf[1024] = {0}, buf1[1024] = {0};
	/* 1. 创建通讯端 */
	s_fd = socket (AF_INET, SOCK_STREAM, 0);
	if (-1 == s_fd) {
		perror ("socket");
		return 1;
	}
	/* 2. 初始化服务器端信息 */
	server.sin_family = AF_INET; // ipv4
	server.sin_port = htons (7778); // port
	inet_pton (AF_INET, argv[1], &server.sin_addr); // copy ip 到服务器
	/* 3. 将s_fd和目的地址连接 */
	ret = connect (s_fd, (SA*)&server, sizeof (server));
	if (-1 == ret) {
		perror ("connect");
		return 2;
	}
	while (1) {
		/* 4. 向服务器发送数据 */
		fgets (buf, sizeof (buf), stdin);
		write (s_fd, buf, sizeof (buf));
		memset (buf, 0, sizeof (buf));
		/* 5. 等待获取服务器响应信息 */
		int r = read (s_fd, buf1, sizeof (buf1));
		/* 6. 将服务器的响应信息输出到显示器 */
		write (1, buf1, r); // 1==STDOUT
		// write (1, "\n", 2);
		memset (buf1, 0, sizeof (buf1));
	}
	/* 7. 关闭连接描述符 */
	close (s_fd);
	return 0;
}

"127.0.0.1" 客户机连接本机服务器的本地环回地址。

三、"实现服务器的并发":使用多进程实现服务器的并发。
在accept之后,调用fork函数产生子进程:
"父进程"关闭连接描述符(关闭conn_fd),继续监听socket是否有客户端连接的到来。
"子进程"关闭socket连接(关闭s_fd),负责和客户端的通讯。
"server - 支持多用户并发"
/** 代码如下 server.c **/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
typedef struct sockaddr SA; 
typedef struct sockaddr_in SA4;
int main(void) { /** 新增变量cli **/
    SA4 serv, cli; //定义服务器的地址变量
    int s_fd, conn_fd;
    int ret;
    char buf[128] = {0};
    char ip[128] = {0}; /** 存放ip地址字符串 **/
    int port; /** 存放端口号 **/
    //<1> 创建socket通讯端
    s_fd = socket(AF_INET, SOCK_STREAM, 0); 
    if(-1 == s_fd) {
        perror("socket");
        return 1;
    }   
    //补充:初始化服务器的信息
    serv.sin_family = AF_INET;
    serv.sin_port = htons(7777); //端口5000以下留给系,可选5000以上
    serv.sin_addr.s_addr = htonl(INADDR_ANY);//长整型主机号(ip)
    //<2> 将服务器的IP地址和端口号与s_fd绑定
    ret = bind(s_fd, (SA *)&serv, sizeof(SA4)); //SA4或serv
    if(-1 == ret) {
        perror("bind");
        return 2;
    }   
    //<3> 监听s_fd
    listen(s_fd, 5); 
    //<4> 阻塞等待客户端的连接
    while(1) {
        /** 将客户端的socket pairs保存在了cli指向的地址里 **/
        int cli_len = sizeof(cli);
        conn_fd = accept(s_fd, (SA *)&cli, &cli_len);
        if(-1 == conn_fd) {
            perror("accept");
            return 3;
        }
        pid_t pid = fork();
        if(pid < 0) {
            perror("fork");
            return 4;
        }
        if(pid > 0) { /** 父进程:负责监听客户端的连接 **/
            close(conn_fd);
            continue;
        } else { /** 子进程:负责和客户端通信 **/
            close(s_fd); /** 取消关闭s_fd就不会报accept **/
            /** 将二进制ip转换为字符串ip地址打印 **/
            char *p = (char *)inet_ntop(AF_INET, &cli.sin_addr, ip, 128);
            printf("客户端ip:%s\n", p);
            printf("用户姓名:");
            if(!strcmp(p, "176.135.11.138")) {
                printf("唐发洪\n");
            }
            /** 从cli结构体中打印端口号 **/
            printf("端口号:%d\n", ntohs(cli.sin_port));
            //<5> 读取客户端发送过来的信息
            int r = read(conn_fd, buf, 127);
            //<6> 加工处理信息
            for(int i = 0; i < r; i++) {
                buf[i] = toupper(buf[i]);
            }
            //给客户端响应
            write(conn_fd, buf, r);
            printf("接收到信息:%s\n", buf);
            //<7> 和这个客户端业务结束,关闭连接
            sleep(10);
exit(0); //关闭子进程
            close(conn_fd);
        }
    }
    return 0;
}

"select"(2) /** 待扩充,nigix使用的就是此多进程方式 **/

四、基于UDP的通讯
"UDP和TCP的区别":
UDP是面向数据包的,而TCP是面向数据流的。
TCP是面向连接的,相对与UDP传输比较"安全,不会丢包"。(如:QQ传文件)
UDP的"传输效率比较高",但是安全性比较差,"容易丢包"。(如:QQ视频)


1. 基于UDP的服务器端模型
<1> 创建socket通讯端
<2> 绑定socket描述符和服务器的socket pairs
<3> recvfrom等待客户端数据的到来
<4> 处理数据
<5> sendto响应客户端


2. 基于TCP的客户端模型
<1> 创建socket通讯端
<2> sendto发送信息给服务器端
<3> recvfrom等待服务器端的响应信息
<4> 处理数据
<5> 关闭socket通讯端-连接描述符(close(参数);)


/** 1.<3> recvfrom等待客户端数据的到来 **/
"recvfrom"(2) "其他的相关函数自行扩充"
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                struct sockaddr *src_addr, socklen_t *addrlen);
功能:从socket接收消息
参数:
"sockfd" socket(2)的返回值
"buf" 接收消息的内存空间地址
"len" 接收消息的长度
"flags" 0
"src_addr" 源的socket pairs;如果为 NULL,addrlen也就为 NULL
"addrlen" src_addr的长度
返回值:
成功 - 返回接收到的字节数
失败 - 返回 -1,errno被设置


/** 1.<5> sendto响应客户端 **/
"sendto"(2)
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
              const struct sockaddr *dest_addr, socklen_t addrlen);
功能:通过socket发送消息
参数:
"sockfd" socket(2)的返回值
"buf" 将buf指定的内存空间里的数据发送出去
"len" buf的长度
"flags" 0
"dest_addr" 目标地址
"addrlen" 目标地址的长度
返回值:
成功 - 返回成功发送的字节数
失败 - 返回 -1,errno被设置


/** 举例验证:
编写程序,实现UDP的通讯,客户端发送字符串,服务器端将客户端的发送过来的字符串转换为大写,响应给客户端。客户端收到服务器端的响应,将响应信息输出到屏幕上。
服务器端 userver.c 
客户端   uclient.c  **/
"userver.c"
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <ctype.h>
typedef struct sockaddr SA; 
typedef struct sockaddr_in SA4;
int main(void) {
    int s_fd;
    SA4 server, client;
    char buf[128] = {0};
    //<1> 创建socket
    s_fd = socket(AF_INET, SOCK_DGRAM, 0); 
    if(-1 == s_fd) {
        perror("socket");
        return 1;
    }   
    //初始化server
    server.sin_family = AF_INET;
    server.sin_port = htons(7779);
    server.sin_addr.s_addr = htonl(INADDR_ANY);
    //<2> 绑定s_fd和服务器的socket pair
    int b = bind(s_fd, (SA *)&server, sizeof(server));
    if(-1 == b) {
        perror("bind");
        return 2;
    }   
    while(1) {
        //<3> 接收数据
        int cli_len = sizeof(client);
        int r = recvfrom(s_fd, buf, 128, 0, (SA *)&client, &cli_len);
        printf("接收到信息:%s\n", buf);
        //<4> 数据处理
        for(int i = 0; i < r; i++) {
            buf[i] = toupper(buf[i]);
        }   
        //<5> 发送给客户端
        sendto(s_fd, buf, r, 0, (SA *)&client, sizeof(client));
        printf("发出的信息:%s\n", buf);
    }   
    close(s_fd);
    return 0;
}

"uclient.c"
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
int main(void) {
    int s_fd;
    char buf[20] = "hello,world!";
    //<1> 创建socket通讯端
    s_fd = socket(AF_INET, SOCK_DGRAM, 0); 
    if(-1 == s_fd) {
        perror("socket");
        return 1;
    }   
    struct sockaddr_in server;
    //初始化server的信息
    server.sin_family = AF_INET;
    server.sin_port = htons(7779);
    inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
    //<2> 发送信息给服务器
    sendto(s_fd, buf, strlen(buf)+1, 0, (struct sockaddr *)&server, sizeof(server));
    //<3> 从服务器端等待响应信息
    bzero(buf, 20);
    int r = recvfrom(s_fd, buf, 20, 0, NULL, NULL);
    //<4> 处理数据
    write(1, buf, r); 
    write(1, "\n", 2); 
    close(s_fd);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姜源Jerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值