目录
一、 TCP编程的函数API(继续)
网络发送数据:send()/write()
ssize_t send(int socket, const void *buffer, size_t length, int flags);
返回值:
成功:实际发送的字节数
失败:-1, 并设置errno
头文件:
#include <sys/types.h> #include <sys/socket.h>
buffer : 发送缓冲区首地址
length : 发送的字节数
flags : 发送方式(通常为0,此时和write()作用一样) send()比write多一个参数flags
特殊的标志:
MSG_DONTWAIT: Enables nonblocking operation; 非阻塞版本
MSG_OOB:用于发送TCP类型的带外数据(out-of-band)
MSG_PEEK:(每次读取都从头开始)
This flag causes the receive operation to return data from
the beginning of the receive queue without removing that
data from the queue. Thus, a subsequent receive call will
return the same data.
网络接收数据:recv()/read()
ssize_t recv(int socket, const void *buffer, size_t length, int flags);
返回值:
成功:实际接收的字节数
失败:-1, 并设置errno
头文件:
#include <sys/types.h> #include <sys/socket.h>
buffer : 发送缓冲区首地址
length : 发送的字节数
flags : 接收方式(通常为0)
write(): #include <unistd.h> ssize_t write(int fd, const void *buf, size_t count);
read(): #include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
read()和write()经常会代替recv()和send(),通常情况下,看程序员的偏好
使用read()/write()和recv()/send()时最好统一
二、UDP编程的API
sendto()发送数据,recvfrom() 接收数据 这两个函数一般在使用UDP协议时使用
代码实现UDP编程:
net.h:
#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include<arpa/inet.h>
#define SERV_PORT 5001
#define SERV_IP_ADDR "127.0.0.1"
#define QUIT_STR "quit"
#endif
server.c:
#include "net.h"
int main(void)
{
int fd = -1;
struct sockaddr_in sin;
/* 1. 创建socket fd */
if ((fd = socket (AF_INET, SOCK_DGRAM, 0)) < 0) { //udp唯一对应DGRAM,TCP是STREAM
perror ("socket");
exit (1);
}
/* 2. 允许绑定地址快速重用 */
int b_reuse = 1;
setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));
/*2. 绑定 */
/*2.1 填充struct sockaddr_in结构体变量 */
bzero (&sin, sizeof (sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (SERV_PORT); //网络字节序的端口号
/* 让服务器程序能绑定在任意的IP上 */
#if 1
sin.sin_addr.s_addr = htonl (INADDR_ANY);
#else
if (inet_pton (AF_INET, SERV_IP_ADDR, (void *) &sin.sin_addr) != 1) {
perror ("inet_pton");
exit (1);
}
#endif
/*2.2 绑定 */
if (bind (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
perror ("bind");
exit (1);
}
char buf[BUFSIZ];
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
printf("\nUDP server started!\n");
while(1) {
bzero(buf, BUFSIZ);
if( recvfrom(fd, buf, BUFSIZ-1, 0,(struct sockaddr *)&cin, &addrlen ) < 0) {
perror("recvfrom");
continue;
}
char ipv4_addr[16];
if (!inet_ntop (AF_INET, (void *) &cin.sin_addr, ipv4_addr, sizeof (cin))) {
perror ("inet_ntop");
exit (1);
}
printf("Recived from(%s:%d), data:%s",ipv4_addr, ntohs(cin.sin_port), buf);
if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Client(%s:%d) is exiting!\n", ipv4_addr, ntohs(cin.sin_port));
}
}
close(fd);
return 0;
}
client.c:
/*udp demo */
/* usage:
* ./client serv_ip serv_port 需要输入ip和端口号*/
#include "net.h"
void usage(char *s)
{
printf("\nThis is udp demo!\n");
printf("\nUsage:\n\t %s serv_ip serv_port",s);
printf("\n\t serv_ip: udp server ip address");
printf("\n\t serv_port: udp server port(serv_port > 5000)\n\n");
}
int main(int argc, char *argv[])
{
int fd = -1;
int port = SERV_PORT;
port = atoi(argv[2]);
if(port < 0 || (port >0 && port <= 5000)) {
usage(argv[0]);
exit(1);
}
struct sockaddr_in sin;
if(argc !=3) {
usage(argv[0]);
exit(1);
}
/* 1. 创建socket fd*/
if( (fd = socket(AF_INET,SOCK_DGRAM, 0)) < 0) { //UDP编程
perror("socket");
exit(1);
}
/*2.1 填充struct sockaddr_in结构体变量 */
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT); //网络字节序的端口号
#if 0
sin.sin_addr.s_addr = inet_addr(argv[1]);//任意IP地址,但是现在客户端需要指定,所以不能用这个
#else
if( inet_pton(AF_INET, argv[1], (void *)&sin.sin_addr) != 1) {
perror("inet_pton");
exit(1);
}
#endif
printf("UDP client started!\n");
char buf[BUFSIZ];
while(1) {
fprintf(stderr,"pls input string:");
bzero(buf, BUFSIZ);//把buf清零
if( fgets(buf, BUFSIZ-1, stdin) ==NULL) {
perror("fgets");
continue;
}
sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&sin, sizeof(sin));
if( !strncasecmp(buf, QUIT_STR, strlen(QUIT_STR))) { //用户输入了quit字符
printf("Client is exited!\n");
break;
}
}
close(fd);
return 0;
}
Makefile:
# Makefile
#
#CROSS_COMPILE = arm-linux-gnu-
CC = $(CROSS_COMPILE)gcc
ifdef CROSS_COMPILE
TARGET = /opt/filesystem
endif
#DEBUG = -g -O0 -Wall
DEBUG = -g -O2
CFLAGS += $(DEBUG)
PROGS = ${patsubst %.c, %, ${wildcard *.c}}
all : $(PROGS)
install: $(PROGS)
ifdef CROSS_COMPILE
mkdir $(TARGET)/root/long_term/io -p
cp $(PROGS) $(TARGET)/root/long_term/io -f
endif
%.o : %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: uninstall clean dist
uninstall :
ifdef CROSS_COMPILE
cd $(TARGET)/root/long_term/io && rm -f $(PROGS)
endif
clean : uninstall
- rm -f $(PROGS) core *.gz
dist: clean
tar czf ../../farsight_network_1st_v1.1_for_1507.tar.gz ../../networks
运行结果:
day5作业:
1. 简述tcp和udp的特点,以及tcp中有序传输的原理?
答:
TCP协议的主要特点
(1)TCP是面向连接的运输层协议;
(2)每一条TCP连接只能有两个端点(即两个套接字),只能是点对点的;
(3)TCP提供可靠的传输服务。传送的数据无差错、不丢失、不重复、按序到达;
(4)TCP提供全双工通信。允许通信双方的应用进程在任何时候都可以发送数据,因为两端都设有发送缓存和接受缓存;
(5)面向字节流。虽然应用程序与TCP交互是一次一个大小不等的数据块,但TCP把这些数据看成一连串无结构的字节流,它不保证接收方收到的数据块和发送方发送的数据块具有对应大小关系,例如,发送方应用程序交给发送方的TCP10个数据块,但就受访的TCP可能只用了4个数据块久保收到的字节流交付给上层的应用程序,但字节流完全一样。
UDP协议特点
(1)UDP是无连接的传输层协议;
(2)UDP使用尽最大努力交付,不保证可靠交付;
(3)UDP是面向报文的,对应用层交下来的报文,不合并,不拆分,保留原报文的边界;
(4)UDP没有拥塞控制,因此即使网络出现拥塞也不会降低发送速率;
(5)UDP支持一对一 一对多 多对多的交互通信;
(6)UDP的首部开销小,只有8字节.
TCP和UDP的区别
(1)TCP是可靠传输,UDP是不可靠传输;
(2)TCP面向连接,UDP无连接;
(3)TCP传输数据有序,UDP不保证数据的有序性;
(4)TCP不保存数据边界,UDP保留数据边界;
(5)TCP传输速度相对UDP较慢;
(6)TCP有流量控制和拥塞控制,UDP没有;
(7)TCP是重量级协议,UDP是轻量级协议;
(8)TCP首部较长20字节,UDP首部较短8字节;
HTTP、HTTPS、FTP、TELNET、SMTP(简单邮件传输协议)协议基于可靠的TCP协议。TFTP、DNS、DHCP、TFTP、SNMP(简单网络管理协议)、RIP基于不可靠的UDP协议
详见TCP和UDP简述及对比 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/259684707
tcp中有序传输的原理
主机每次发送数据时,TCP就给每个数据包分配一个序列号并且在一个特定的时间内等待接收主机对分配的这个序列号进行确认,如果发送主机在一个特定时间内没有收到接收主机的确认,则发送主机会重传此数据包。接收主机利用序列号对接收的数据进行确认,以便检测对方发送的数据是否有丢失或者乱序等,接收主机一旦收到已经顺序化的数据,它就将这些数据按正确的顺序重组成数据流并传递到高层进行处理。
2. 请编写支持多客户端的UDP服务器和客户端通信的代码
(作业要求:做作业的时候不要再翻看视频上的教程,对函数理解不明白的全部通过man手册去查看,
自己思考框架,使用makefile编译,然后将测试的记录和结果添加到readme.txt文件中提交上来,代码实现完成测试通过后
再提交作业,网络部分学习不写代码不测试看不出问题的,良好的习惯帮助你们快速成长。)
对比上面的代码,只修改了server.c
server.c:
#include "net.h"
#include<sys/stat.h>
#include<fcntl.h>
int main(void)
{
int fd = -1;
struct sockaddr_in sin;
/* 1. 创建socket fd */
if ((fd = socket (AF_INET, SOCK_DGRAM, 0)) < 0) { //udp唯一对应DGRAM,TCP是STREAM
perror ("socket");
exit (1);
}
/* 2. 允许绑定地址快速重用 */
int b_reuse = 1;
setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));
/*2. 绑定 */
/*2.1 填充struct sockaddr_in结构体变量 */
bzero (&sin, sizeof (sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (SERV_PORT); //网络字节序的端口号
/* 让服务器程序能绑定在任意的IP上 */
#if 1
sin.sin_addr.s_addr = htonl (INADDR_ANY);
#else
if (inet_pton (AF_INET, SERV_IP_ADDR, (void *) &sin.sin_addr) != 1) {
perror ("inet_pton");
exit (1);
}
#endif
/*2.2 绑定 */
if (bind (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
perror ("bind");
exit (1);
}
char buf[BUFSIZ];
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
printf("\nUDP server started!\n");
while(1) {
int fd2,ret2;
int buf2[100];
fd2 = open("readme.txt",O_RDWR|O_CREAT|O_APPEND,0666);
if(fd2<0){
printf("open file err\n");
return 0;
}
bzero(buf, BUFSIZ);
if( recvfrom(fd, buf, BUFSIZ-1, 0,(struct sockaddr *)&cin, &addrlen ) < 0) {
perror("recvfrom");
continue;
}
char ipv4_addr[16];
if (!inet_ntop (AF_INET, (void *) &cin.sin_addr, ipv4_addr, sizeof (cin))) {
perror ("inet_ntop");
exit (1);
}
sprintf((void*)buf2,"Receive from(%s:%d),data:%s",ipv4_addr,ntohs(cin.sin_port),buf);
ret2 = write(fd2,buf2,strlen((void*)buf2));
if(ret2<0){
perror("write");
close(fd2);
}
close(fd2);
printf("Recived from(%s:%d), data:%s",ipv4_addr, ntohs(cin.sin_port), buf);
if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Client(%s:%d) is exiting!\n", ipv4_addr, ntohs(cin.sin_port));
}
}
close(fd);
return 0;
}
运行结果:
readme.txt: