Linux 网络编程——socket 网络编程
文章目录
- 一、网络基础
- TCP/UDP对比
- TCP/IP协议族体系
- socket
- IP地址
- IP地址转化API
- inet_addr()
- inet_aton()
- inet_ntoa()
- inet_pton() /inet_ntop()
- 端口号作用
- 端口号转化字节序API
- 字节序
- 字节序转换api
- 二、TCP 编程 API
- 服务端开发步骤
- 服务端开发函数
- 1. 创建套接字:
- 2. 为套接字添加信息
- 3. 监听:
- 4. 连接:
- 5. 数据交互第一套API
- 5. 数据交互的第二套API
- 客户端开发步骤
- 客户端开发函数
- 1. 创建套接字
- 2. 连接
- 三、TCP并发服务器
- 1.TCP并发服务器多线程
- 2.TCP并发服务器多进程
- 四、UDP 编程API
- bind:
- receivefrom:
- sendto:
- send 函数
- recv 函数
- 五、UDP编程
- UDP服务端
- UDP客户端
- 六、实现双方聊天
一、网络基础
- 多进程之间的通信通过内核,而多机通信需要使用网络
- 数据传输:协议,即数据格式
- 每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由
TCP/UDP对比
- TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前,不需 要建立连接
- TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
- TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等) - 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
- TCP首部开销20字节;UDP的首部开销小,只有8个字节
- TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
TCP/IP协议族体系
TCP/IP协议族体系是Internet事实上的工业标准。
一共有四层
应用层 | Relnet,FTP,HTTP,DNS,SMTP等 |
---|---|
传输层 | TCP和UDP |
网络层 | IP,ICMP和IGMP,端到端传输 |
网络接口和物理层 | 以太网,令牌环网,FDDI,wifi,gps/2G/3G/4G,驱动(屏蔽硬件差异) |
socket
是一个编程接口,是一个特殊的文件描述符(对他执行IO的操作函数,比如read,write,close等),并不仅限于TCP/IP协议,面向连接TCP,无连接UDP。
socket代表网络编程的一种资源
sock与HTTP和TCP之间的关系可以看:计算机网络——SOCKET、TCP、HTTP之间的区别与联系
分类:
- 流式套接字(SOCK_STREAM)。唯一对应 TCP 提供了一个面向连接,可靠的数据传输服务,数据无差错,无重复的发送顺序接收。内射击流量控制,避免数据流淹没慢的接收方。数据被看作式字节流,无长度限制。
- 数据包套接字(SOCK_DGRAM)。唯一对应UDP提供无连接服务器,数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
- 原始套接字(SOCK_RAW)。对应多个协议,发送穿透了传输层可以对较低层次协议(网络层)如IP,ICMP直接访问【跳过传输层】。
IP地址
IP地址是Internet中主机的标识,Internet中的主机要与别的机器通信必须具有一个IP地址,IP地址为32为(Ipv4)或者128位(Ipv6),每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由
表示的形式:常用点分形式,如202.38.64.10,最后都会转化成一个32位的无符号整数
mobileIPV6:local IP(本地注册的IP),roma IP(漫游IP)
特殊的IP地址:
- 局域网IP:192.xxx.xxx.xxx或10.xxx.xxx.xxx
- 广播IP:xxx.xxx.xxx.255 255.255.255.255(全网广播)网络风暴
- 组播IP:224.xxx.xxx.xxx ~ 239.xxx.xxx.xxx
IP地址转化API
inet_addr()
in_addr_t inet_addr(const char *cp);
- 返回转化后的地址
- 将strptr所指向的字符串转化为32位的网络字节序二进制
- 用在bind、send(TCP)、sendto(UDP)等发送报文的接口前
- 可以用在linux平台、windows平台,但是只支持ipv4地址,不支持ipv6地址
- 局限性:不能用于255.255.255.255的转化
inet_aton()
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char* straddr,struct in_addr *addrp);
- 将strptr所指向的字符串转化为32位的网络字节序二进制
inet_ntoa()
char *inet_ntoa(struct in_addr in);
- 将一个32位网络字节序二进制地址转换为点分十进制的字符串
char* inet_ntoa(struct in_addr inaddr(AF_INET,); //把网络格式的ip地址转为字符串形式
- 用在recv(TCP)、recvfrom(UDP)等接收报文的接口前面
inet_pton() /inet_ntop()
inet_pton() /inet_ntop()
inet_pton(int af, const char *src, void *dst)
转换字符串到网络地址,- 第一个参数af是地址族(AF_INET或AF_INET6),
- 第二个参数*src是来源地址,
- 第三个参数* dst接收转换后的数据存放的地址;
- 将IPV4/IPV6的地址转化为binary格式;能够处理255.255.255.255的转化
inet_ntop(int af, const void *src, char *dst, socklen_t size);
转换网络二进制结构到ASCII类型的地址,参数的作用和inet_pton相同,只是多了一个参数socklen_t size,他是所指向缓存区dst的大小,避免溢出,如果缓存区太小无法存储地址的值,则返回一个空指针,并将errno置为ENOSPC。
端口号作用
- 一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等。这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。
- 实际上是通过“IP地址+端口号”来区 分不同的服务的。端口提供了一种访问通道,服务器一般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69。
- 16位数字,1-65535,
- 为了区分一台主机接收到的数据包应该转交给哪个任务进程处理,使用端口号来区别
- 预留端口,1-1023(FTP:24, SSH:22, HTTP: 80 ,HTTPS :469)
- 保留端口:1024-5000(不建议使用)
- 可以使用的端口: 5000~65535
- TCP端口号于UDP端口号独立
- 网络里的通信是由 IP地址+端口号 来决定的
端口号转化字节序API
- 端口号需要传递到网络上,需要使用api将主机字节序转化为网络字节序
- 当服务器打印客户端端口号时,需要使用api将网络字节序转化为主机字节序
#include <netinet/in.h>
uint16_t htons(uint16_t host16bitvalue); //主机字节序到网络字节序
uint16_t ntohs(uint16_t host16bitvalue); //网络字节序到主机字节序
字节序
定义:字节序是指多字节数据在计算机内存中存储或网络传输时各字节的存储顺序
常见的字节序:
- Little endian 小端字节序【将低序字节存储在起始地址】
- Big endian 大端字节序 【将高序字节存储在起始地址】
字节序是指不同的CPU访问内存中的多字节数据时候,存在大小端的问题
- 如果CPU访问的是字符串,则不存在大小端问题
- 把给定系统所采用的字节序称为主机字节序,为了避免不同类别主机之间在数据交换时由于对字节序的不同而导致差错,引入网络字节序
- 网络字节序=大端字节序
- x86/ARM系列的CPU采用的都是小端字节序
- power/miop:arm作为路由时,采用的是大端模式
字节序转换api
#include <arpa/inet.h>uint16_t htons(uint16_t host16bitvalue); //主机字节序到网络字节序
uint32_t htonl(uint32_t host32bitvalue); //主机字节序到网络字节序uint16_t ntohs(uint16_t net16bitvalue); //网络字节序到主机字节序
uint32_t ntohl(uint32_t net32bitvalue); //网络字节序到主机字节序h代表host,n代表net,s代表short(两个字节),l代表long(4个字节),
二、TCP 编程 API
服务端开发步骤
- 创建套接字:socket()返回值是套接字的描述符
- 为套接字添加信息(IP地址和端口号):bind()
- 监听网络连接:listen()
- 监听到有客户端接入,接收一个连接:accept()
- 数据交互:read()和write()
- 关闭套接字,断开连接:close()
服务端开发函数
1. 创建套接字:
函数原型:
int socket(int domain, int type, int protocol);
参数:
返回值:
- 成功则返回文件描述符
- 失败则返回-1
注意:
- 如果是IPV6编程,要使用struct sockddr_in6结构体(man 7 IPV6),通常使用struct sockaddr_storage来编程。
2. 为套接字添加信息
函数原型:
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
参数:
- addrlen:第三个参数为结构体的长度,使用sizeof(struct sockaddr_in)
注意:
第二个参数需要强制类型装换为(struct sockaddr*),因为我们使用的addr指针变量是由结构体struct sockaddr_in 创建的(替换了sockaddr结构体)
使用sockaddr_in结构体替换sockaddr结构体:
struct in_addr{uint32_t s_addr;
}
TCP连接协议族选:AF_INET
第三个参数为IP地址结构体,配置结构体变量in_addr的成员s_addr
bind函数使用实例:
struct sokeaddr_in s_addr;//定义一个由sokeaddr_in创建的对象s_addr
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8989);//端口号需要转化为网络字节序//IP地址转化的4种方式
//第一种:
inet_aton("127.0.0.1",&s_addr.sin_addr)//由上面定义的inet_aton第二个参数结构体指针,指向in_addr 结构体,所以取到该结构体,然后取其地址值
//第二种:
s_addr.sin_addr.s_addr = inet_addr("127.0.0.1")//char *inet_ntoa(struct in_addr in);
//第三种:
inet_pton(AF_INET,"127.0.0.1",(void *)s_addr.sin_addr.s_addr); //成功返回值为1
//第四种:应用在服务端,自动获取服务器IP地址,
s_addr.sin_addr.s_addr = INADDR_ANY;
3. 监听:
函数原型:
int listen(int sockfd,int backlog);
参数:
返回值:
- 成功返回0
- 失败返回-1
注意:
- 第二个参数一般填写5
- 内核中服务器的套接字fd会维护2个链表
- 正在三次握手的客户端链表(数量=2*backlog+1)
- 已经建立好连接的客户端链表(已经完成三次握手分配好了的newfd)
4. 连接:
- 当服务器开启监听,内核会维护两个队列,此时accept会查询队列中完成三次握手的客户机,若有则与其连接,返回新的套接字描述符
函数原型:
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
返回值:
- 是新的套接字描述符,用于后续读写操作,因为原先的套接字描述符可能被多个客户申请连接使用
参数与功能:
注意:
- 第二三个参数是客户端的信息,如果不需要获取则可以为NULL。
- 如果accept第二三个参数都有,则可以用ip地址转换api打印客户机的ip地址或端口号
代码实例:
int newfd = -1;
struct sockaddr_in c_addr;
socklen_t addrlen = sizeof(c_addr);
newfd = accept(fd,(struct sockaddr *)&c_addr,&addrlen );
if(newfd <0){perror("accept");exit(1);
}
char ipv4_addr[16];
id(!inet_ntop(AF_INET,(void *)&c_addr,sin_addr,ipv4_addr,sizeof(c_addr))){perror("inet_ntop");exit(1);
}
printf("Client:(%s,%d)is connect!\n",ipv4_addr,ntohs(c_addr.sin_port));
- 第二个参数与bind函数一样,所以需要使用memset函数进行数据清空
#include <string.h>
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
- 第二个参数也需要强制类型装换为(struct sockaddr*)
5. 数据交互第一套API
函数原型:
ssize_t write(int fd,const void *buf,size_t nbytes);
ssize_t read(int fd,void *buf,size_t nbyte);
//函数返回值为读写的字节个数,错误则返回-1
//返回0表示客户端退出
注意:
此时的fd为accept的返回值,而不是socket()返回值
在套接字通信中进行字节读取函数与IO读取的略语有区别,因为他们输入或输出的字节数比请求的少
网络I/O操作:(一)read()/write()(二)recv()/send()(三)readv()/writev()(四)recvmsg()/sendmsg()(五)recvfrom()/sendto()
5. 数据交互的第二套API
- 在TCP套接字上发送数据函数:有连接
ssize_t send(int s,const void *msg,size_t len,int flags);
参数:
- sockfd:socket函数返回的fd
- buffer:发送缓冲区首地址
- length:发送的字节
- flags:发送方式(通常为0),作用和write一样
- MSG_DONTWAIT,非阻塞
- MSG_OOB:用于TCP类型的带外数据(out of band)
返回值:
- 成功:实际发送的字节数
- 失败:-1,并设置errno
- 在TCP套接字上接收数据函数:有连接
ssize_t recv(int s,void *buf,size_t len,int flags);
flag:一般填0,和read作用一样
特殊的标志:
- MSG_DONTWAIT
- MSG_OOB:读取带外数据
- MSG_PEEK:流
客户端开发步骤
客户端开发函数
1. 创建套接字
同服务端开发函数一样
2. 连接
函数原型:
int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
参数:
注意:
- 头文件中<linux/in.h> 会与 <netinet/in.h>冲突
- connect和accept都会阻塞
三、TCP并发服务器
1.TCP并发服务器多线程
server.c
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>void *client_data_hadler(void* arg);
int main(int argc, char **argv)
{int s_fd;int c_fd;int Ret;pthread_t tid;int mark = 0;char msg[128] = {0};struct sockaddr_in s_addr;struct sockaddr_in c_addr;if (argc != 3){printf("param is not good\n");exit(-1);}memset(&s_addr, 0, sizeof(struct sockaddr_in));memset(&c_addr, 0, sizeof(struct sockaddr_in));// 1. sockets_fd = socket(AF_INET, SOCK_STREAM, 0);if (s_fd == -1){perror("socket");exit(-1);}// 2. bind 配置 struct sockaddr_in 结构体,绑定时再转换成 struct sockaddr * 结构体类型s_addr.sin_family = AF_INET;s_addr.sin_port = htons(atoi(argv[2]));inet_aton(argv[1], &s_addr.sin_addr);Ret = bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));if (Ret == -1){perror("bind");exit(-1);}// 3. listenRet = listen(s_fd, 10);if (Ret == -1){perror("listen");exit(-1);}// 4. acceptint clen = sizeof(struct sockaddr_in);while (1){c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen);if (c_fd == -1){perror("accept");break;}mark++;printf("get connect: %s\n", inet_ntoa(c_addr.sin_addr));sprintf(msg, "welcom No.%d client", mark); // 字符串拼接write(c_fd, msg, strlen(msg));pthread_create(&tid,NULL,client_data_hadler,(void*)&c_fd);}close(s_fd);return 0;
}
//线程处理函数
void *client_data_hadler(void* arg){int c_fd = *(int *)arg;int n_read;char readBuf[128];//在服务端打印线程的id值printf("thread c_fd= %d\n",c_fd);while (1){memset(readBuf, 0, sizeof(readBuf));n_read = read(c_fd, readBuf, 128);if (n_read == -1){perror("read");}else if (n_read > 0){printf("\nget command: %s \n", readBuf);}else{printf("client quit\n");break;}}close(c_fd);}
client.c
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char **argv)
{int c_fd;int n_read;char readBuf[128];int tmp;char msg[128] = {0};struct sockaddr_in c_addr;memset(&c_addr, 0, sizeof(struct sockaddr_in));if (argc != 3){printf("param is not good\n");exit(-1);}// 1. socket 配置 struct sockaddr_in 结构体,连接时再转换成 struct sockaddr * 结构体类型c_fd = socket(AF_INET, SOCK_STREAM, 0);if (c_fd == -1){perror("socket");exit(-1);}// 2.connectc_addr.sin_family = AF_INET;c_addr.sin_port = htons(atoi(argv[2]));inet_aton(argv[1], &c_addr.sin_addr);if (connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr)) == -1){perror("connect");exit(-1);}// 连接成功memset(readBuf, 0, sizeof(readBuf));n_read = read(c_fd, readBuf, 128);if (n_read == -1){perror("read");}else{printf("\nget:%s\n", readBuf);}// 不断的接收终端的命令发送至 c_fdwhile (1){memset(msg, 0, sizeof(msg));printf("input: ");fgets(msg,128,stdin);write(c_fd, msg, strlen(msg));}return 0;
}
2.TCP并发服务器多进程
server.c
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char **argv)
{int s_fd;int c_fd;int n_read;int iRet;char readBuf[128];pid_t pid;int mark = 0;char msg[128] = {0};struct sockaddr_in s_addr;struct sockaddr_in c_addr;if (argc != 3){printf("param is not good\n");exit(-1);}memset(&s_addr, 0, sizeof(struct sockaddr_in));memset(&c_addr, 0, sizeof(struct sockaddr_in));// 1. sockets_fd = socket(AF_INET, SOCK_STREAM, 0);if (s_fd == -1){perror("socket");exit(-1);}// 2. bind 配置 struct sockaddr_in 结构体,绑定时再转换成 struct sockaddr * 结构体类型s_addr.sin_family = AF_INET;s_addr.sin_port = htons(atoi(argv[2]));inet_aton(argv[1], &s_addr.sin_addr);iRet = bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));if (iRet == -1){perror("bind");exit(-1);}// 3. listeniRet = listen(s_fd, 10);if (iRet == -1){perror("listen");exit(-1);}// 4. acceptint clen = sizeof(struct sockaddr_in);while (1){c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen);if (c_fd == -1){perror("accept");break;}mark++;printf("get connect: %s\n", inet_ntoa(c_addr.sin_addr));pid = fork();if(pid < 0){perror("fork");break;}// 接收到 connect 后用子进程处理if (pid == 0){sprintf(msg, "welcom No.%d client", mark); // 字符串拼接write(c_fd, msg, strlen(msg));// 在子进程里面不断的读取客户端发送到 c_fd 的内容while (1){memset(readBuf, 0, sizeof(readBuf));n_read = read(c_fd, readBuf, 128);if (n_read == -1){perror("read");}else if (n_read > 0){printf("\nget command: %s \n", readBuf);}else{printf("client quit\n");break;}}}}return 0;
}
client.c
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char **argv)
{int c_fd;int n_read;char readBuf[128];int tmp;char msg[128] = {0};struct sockaddr_in c_addr;memset(&c_addr, 0, sizeof(struct sockaddr_in));if (argc != 3){printf("param is not good\n");exit(-1);}// 1. socket 配置 struct sockaddr_in 结构体,连接时再转换成 struct sockaddr * 结构体类型c_fd = socket(AF_INET, SOCK_STREAM, 0);if (c_fd == -1){perror("socket");exit(-1);}// 2.connectc_addr.sin_family = AF_INET;c_addr.sin_port = htons(atoi(argv[2]));inet_aton(argv[1], &c_addr.sin_addr);if (connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr)) == -1){perror("connect");exit(-1);}// 连接成功memset(readBuf, 0, sizeof(readBuf));n_read = read(c_fd, readBuf, 128);if (n_read == -1){perror("read");}else{printf("\nget:%s\n", readBuf);}// 不断的接收终端的命令发送至 c_fdwhile (1){memset(msg, 0, sizeof(msg));printf("input: ");gets(msg);write(c_fd, msg, strlen(msg));}return 0;
}
四、UDP 编程API
bind:
- 绑定服务器:TCP地址和端口号
receivefrom:
- 阻塞等待客户端数据
#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);参数:sockfd :数据报套接字,socket函数返回值buf :内存地址len :接收数据的大小flags :标志位 0src_addr :发送端的addr结构体地址addrlen :addr结构体的长度的地址
返回值:成功则返回接收的字节数,出错返回-1
sendto:
- 指定服务器的IP地址和端口号,要发送的数据
#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);参数:sockfd :数据报套接字,socket函数返回值buf :内存地址len :接收数据的大小flags :标志位 0src_addr :接收端的addr结构体地址addrlen :addr结构体的长度的地址【这里不是取地址值,与receivfrom不一样】
返回值:成功则返回发送的字节数,出错返回-1
send 函数
ssize_t send(int sockfd,const void *buf,size_t len,int flags);参数:sockfd:socket函数返回的fdbuffer:发送缓冲区首地址length:发送的字节flags:发送方式(通常为0),作用和write一样MSG_DONTWAIT,非阻塞MSG_OOB:用于TCP类型的带外数据(out of band)返回值:成功:实际发送的字节数失败:-1,并设置errno
recv 函数
int recv( SOCKET s, char FAR *buf, int len, int flags);flag:一般填0,和read作用一样特殊的标志:MSG_DONTWAITMSG_OOB:读取带外数据MSG_PEEK:流
五、UDP编程
UDP服务端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, const char *argv[])
{//1.创建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){perror("socket");return -1;}int b_reuser = 1;//地址映射,允许地址快速重新使用,因为四次setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuser,sizeof(int));//2. 绑定服务器的IP地址和端口号struct sockaddr_in serveraddr ={0};memset(&serveraddr,0,sizeof(struct sockaddr_in));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(8080);//serveraddr.sin_addr.s_addr = inet_addr("0");serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);int len = sizeof(serveraddr);int ret = bind(sockfd, (struct sockaddr *)&serveraddr, len );if(ret == -1){perror("bind");return -1;}//3.收发数据 struct sockaddr_in clientaddr={0};socklen_t addrlen=sizeof(clientaddr);char buf[64] = {0};while(1){memset(&clientaddr,0,sizeof(struct sockaddr_in));//可以理解为将read函数写在了recvfrom里面,从buf里面读取int n=recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&clientaddr,&addrlen);if(n<0){perror("recvfrom");continue;//继续读值}//将客户端的ip地址和端口号打印出来printf("client ip:%s client port:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));printf("message:%s\n", buf);if(strcmp(buf,"quit")==0){printf("client(%s,%d)is exiting\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port)); break;//退出}memset(buf, 0, 64); //数组清零}close(sockfd);return 0;
}
UDP客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, const char *argv[])
{if(argc<3){printf("usage ./可执行文件 服务器IP地址 端口号");return -1;}//1.创建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){perror("socket");return -1;}//服务器的IP地址和端口号struct sockaddr_in serveraddr = {0},clientaddr={0};serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));serveraddr.sin_addr.s_addr = inet_addr(argv[1]);//serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);int len = sizeof(serveraddr);//2.收发数据char buf[64] = {0};int l=sizeof(clientaddr);printf("server ip:%s server port:%d\n", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port));while(1){printf("input:");fgets(buf,1024,stdin);buf[strlen(buf)-1]='\0'; int n=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&serveraddr,len);if(n<0){perror("sendto");return -1;}if(strcmp(buf,"quit")==0)break;//printf("message:%s\n", buf);memset(buf, 0, 64); //数组清零}close(sockfd);return 0;
}
六、实现双方聊天
在网络进程中,父进程等待客户端的服务请求,当这种请求到达时,父进程调用fork,使得子进程处理该请求,父进程继续等待下一个服务请求的到达
服务端
#include <stdio.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>int main (int argc,char ** argv){int s_fd;int c_fd;int n_read;int ret;char readBuf[128];char msg[128]={0};struct sockaddr_in s_addr;struct sockaddr_in c_addr;if(argc !=3){printf("param is not enough");exit(-1);}memset(&s_addr,0,sizeof(struct sockaddr_in));memset(&c_addr,0,sizeof(struct sockaddr_in));s_fd = socket(AF_INET,SOCK_STREAM,0);if(s_fd ==-1){perror("socket");exit(-1);}s_addr.sin_family =AF_INET;s_addr.sin_port = htons(atoi(argv[2]));inet_aton(argv[1],&s_addr.sin_addr);ret = bind(s_fd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in));if (ret == -1){perror("bind");exit(-1);}ret = listen(s_fd,10);{perror("listen");exit(-1);}int clen = sizeof(struct sockaddr_in);while(1){c_fd = accept(s_fd,(struct sockaddr*)&c_addr,&clen);if(c_fd == -1 ){perror("accept");exit(-1);}//打印连接成功的客户端的IP地址printf("get connect: %s\n", inet_ntoa(c_addr.sin_addr));if(fork()==0){if(fork()==0){while(1){memset(msg,0,sizeof(msg));printf("input:");fgets(msg,128,stdin);write(c_fd,msg,strlen(msg));}}while(1){memset(readBuf,0,sizeof(readBuf));n_read = read(c_fd,readBuf,128);if(n_read == -1){perror("read");}else{printf("get message from client:%s\n",readBuf);}}break;}}return 0;
}
客户端
#include <stdio.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>int main (int argc,char ** argv){int c_fd;int n_read;char readBuf[128];char msg[128]={0};struct sockaddr_in c_addr;memset(&c_addr,0,sizeof(struct sockaddr_in));if(argc !=3){printf("param is not enough");exit(-1);}c_fd = socket(AF_INET,SOCK_STREAM,0);if(c_fd ==-1){perror("socket");exit(-1);}c_addr.sin_family =AF_INET;c_addr.sin_port = htons(atoi(argv[2]));inet_aton(argv[1],&c_addr.sin_addr);if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr))==-1){perror("connect");exit(-1);}while(1){if(fork()==0){while(1){memset(msg,0,sizeof(msg));printf("input:");fgets(msg,128,stdin);write(c_fd,msg,strlen(msg));}}while(1){memset(readBuf,0,sizeof(readBuf));n_read = read(c_fd,readBuf,128);if(n_read == -1){perror("read");}else{printf("get message from server:%d,%s\n",n_read,readBuf); }}break;}return 0;
}
执行效果:
注意:
- gcc并没有禁止gets. 只是会提示warning。如果设置了把所有warning处理为error才会导致无法使用。可以用fgets来代替。fgets(s, max_len, stdin);等效于gets(s)
- char s[200];
- fgets(s, 200, stdin);
优秀博文:
Linux网络编程|socket编程 原创
Linux Socket 网络编程
Linux 网络编程——socket 网络编程相关推荐
- Linux应用(四)socket网络编程
文章目录 一 简略了解网络 1 三种socket 1.1 SOCK_STREAM 1.2 SOCK_DGRAM 1.3 SOCK_RAW 2 面向连接和无连接 2.1 面向连接 2.2 无连接 3 网 ...
- Linux中的socket网络编程
一.socket网络编程结构 tcp socket网络编程的结构: 客户端:socket-------------------------connect--------(send/recv)----- ...
- Linux之C++ socket通信编程
服务端:服务器端先初始化socket,然后与端口绑定,对端口进行监听,调用accept阻塞,等待客户端连接. socket() -> bind() -> listen() -> ac ...
- Linux学习之----socket网络编程基础
分层模型 OSI七层模型 1.物理层:主要定义物理设备标准,如网线的接口类型.光纤的接口类型.各种传输介质的传输速率等.它的主要作用是传输比特流(就是由1.0转化为电流强弱来进行传输,到达目的地后再转 ...
- Linux C++/Java/Web/OC Socket网络编程
一,Linux C++ Socket网络编程 1.什么是TCP/IP.UDP? TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制 ...
- Linux Kernel TCP/IP Stack — Socket Layer — TCP/UDP Socket 网络编程
目录 文章目录 目录 TCP/UDP Socket 逻辑架构 创建 Socket 绑定 Socket 请求建立 Socket 连接 监听 Socket 接受请求 关闭连接 数据的发送和接收 send ...
- Linux下Socket网络编程
什么是Socket Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序.要学Internet上的TCP/IP网络编程, ...
- 基于Linux的socket网络编程项目——游侠手机商城
基于Linux的socket网络编程项目--游侠手机商城 一.项目说明 二.项目使用的技术 三.客户端搭建 四.服务器端搭建 一.项目说明 本项目是一个仿真手机商城类系统,基本功能: 登录界面功能:用 ...
- Linux网络编程——socket、bind、listen、accpet、connect、read和write
Linux网络编程 基础理论 1.TCP/UDP/端口号 2.字节序 一.socket服务器与客户端的开发步骤 二.具体使用步骤 1.socket(创建连接协议) 2.bind(地址准备好) 3.li ...
最新文章
- 好物推荐 | 轻薄神器,妈妈再也不用担心我的颈椎了
- javascript正则表达式小结
- linux平台性能监控系统,Linux系统性能监控
- java求面积_Java之简单图形面积计算
- 如何安装部署秋色园QBlog站点
- hdu 6106 Classes
- shell常用命令总结总结
- 自动登录360,百度
- Postman----Presets(预先设置)的使用
- python自动化_python自动化测试-Behave框架的用法介绍 - python测试学习
- php隐藏做上传图片,php做图片上传功能
- WordPress主题LensNews1.8模板源码,WP多功能新闻积分商城主题
- echarts地图显示问题
- vs2015编译纯ASM文件
- ubuntu移动硬盘打不开
- 字节跳动2020春招笔试 - 研发岗位(Java、C++、大数据)
- 大数据全样而非抽样原理_一文带你了解什么是大数据
- 计算机画分段函数,《几何画板》:绘制分段函数的图像 -电脑资料
- 互联网寒冬——“大裁员”
- 心电图学习笔记(1)
热门文章
- 热释电传感器三个引脚_热释电红外传感器模块的三个端口需要注意区别不同功能。...
- 2023最新CISP模拟考试题库及答案(一)
- 判赔20万!星愿浏览器因拦截广告被优酷起诉;苹果调查iPhone 14 Pro传输数据后卡死问题|极客头条...
- thinkphp5.0.23
- 领健医美整形美容医院管理系统对于医疗整形美容行业研究
- 高德地图:输入关键字提示匹配信息(AMap.Autocomplete)
- Eclipse中设置#ifdef中的颜色
- 解读如何更好的定义销售类软文撰写角度
- 时尚潮流又回归复古啦~
- html做空白简历,第二天:给自己做一个在线简历吧-IFE