参考:(1条消息) 【Linux系统编程】网络编程_大头1213的博客-CSDN博客
- 概述
1、tcp与udp的区别
1.TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接;
2.TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付;
3. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等);
4. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信;
5. TCP首部开销20字节;UDP的首部开销小,只有8个字节;
6. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。
2、端口号
端口号的主要作用是表示一台计算机中的特定进程所提供的服务。网络中的计算机是通过IP地址来代表其身份的,它只能表示某台特定的计算机,但是一台计算机上可以同时提供很多个服务,如数据库服务、FTP服务、Web服务等,我们就通过端口号来区别相同计算机所提供的这些不同的服务,如常见的端口号21表示的是FTP服务,端口号23表示的是Telnet服务,端口号25指的是SMTP服务等。端口号一般习惯为4位整数,在同一台计算机上端口号不能重复,否则,就会产生端口号冲突。
3.IP地址
IP地址是指互联网协议地址,是我们进行TCP/IP通讯(因特网互联协议)的基础,每个连接到网络上的计算机都必须有一个IP地址,类似于打电话的”电话号码“。
二.字节序
1、字节序概念:
字节序是计算机存储多字节数据的方式,目前主流的方式有:大端字节序和小端字节序,字节序主要是针对多字节的数据类型,比如 short、int 等类型,在跨平台和网络编程中,时常要考虑的问题。
1. Big-Endian(大端字节序):高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
2.Little-Endian(小端字节序):低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
3.网络字节序 = 大端字节序
2、字节序的深入
例如:
对于数据 0x12345678,假设从地址0x4000开始存放,在大端和小端模式下,存放的位置分别为:
内存地址 | 小端模式 | 大端模式 |
0x4003 | 0x12 | 0x78 |
0x4002 | 0x34 | 0x56 |
0x4001 | 0x56 | 0x34 |
0x4000 | 0x78 | 0x12 |
采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。
小端存储后:0x78563412 大端存储后:0x12345678
3、字节序转换的相关API
#include <netinet/in.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个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取
三、socket(套接字)
socket是对TCP/IP协议的封装,它的出现只是使得程序员更方便地使用TCP/IP协议栈而已。socket本身并不是协议,它是应用层与TCP/IP协议族通信的中间软件抽象层,是一组调用接口(TCP/IP网络的API函数)
总流程:
服务端流程:
客户端流程:
1、使用socket()函数创建套接字
2、调用connect()函数建立一个与TCP服务器的连接
3、发送数据请求,接收服务器的数据应答
4、终止连接
四、相关函数
1.socket函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
2.bind() 函数
用于绑定IP地址和端口号到socket fd
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//sockfd:是一个socket描述符
//addr:是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针,指向要绑定给sockfd的协议地址结构这个地址结构根据地址创建socket 时的地址协议族的不同而不同
3.listen()函数
listen函数使用主动连接 套接字 变为被连接 套接口 ,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。 在 TCP 服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。 listen函数一般在调用bind之后-调用accept之前调用。
int listen(int sockfd, int backlog);
//参数:
//sockfd:sockfd是socket系统调用返回的服务器端socket描述符
//backlog:backlog指定在请求队列中允许的最大请求数
4.accept()函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
5.connect()函数
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
地址转换API
int inet_aton(const char* straddr,struct in_addr *addrp);
//把字符串形式的“192.168.1.123”转为网络能识别的格式
char* inet_ntoa(struct in_addr inaddr);
//把网络格式的ip地址转为字符串形式
6.数据收发
五、编程实现
实现双方聊天
server.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
int s_fd;
int c_fd;
int n_read;
char readBuf[128];
char *msg="i get your connect";//定义一个指针字符串
struct sockaddr_in s_addr;//定义服务端 sockaddr_in类型的结构体s_addr
struct sockaddr_in c_addr;//定义客户端 sockaddr_in类型的结构体c_addr
memset(&s_addr,0,sizeof(struct sockaddr_in));//为s_addr开辟内存空间
memset(&c_addr,0,sizeof(struct sockaddr_in));//为c_addr开辟内存空间
//1.socket
s_fd=socket(AF_INET,SOCK_STREAM,0);//创建套接字,返回一个描述符s_fd
if(s_fd == -1){
perror("socket");
exit(-1);
}
s_addr.sin_family=AF_INET;//设置协议族为tcp协议
s_addr.sin_port=htons(atoi(argv[2]));//设置端口号
inet_aton(argv[1],&s_addr.sin_addr);//把字符串形式的“192.168.1.123”转为网络能识别的格式
//2.bind
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//绑定套接字添加信息
//3.listen
listen(s_fd,10);//设置套接字为监听模式
//4.accept
int clen=sizeof(struct sockaddr_in);
while(1){
c_fd =accept(s_fd,(struct sockaddr *)&c_addr,&clen);//接收客户端的连接请求,返回描述符c_fd
if(c_fd == -1){
perror("accept");
}
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));//获取客户端地址,把网络格式的ip地址转为字符串形式
if(fork() == 0){
//5.read
n_read=read(c_fd,readBuf,128);//创建子进程去读取数据
if(n_read ==-1){
perror("read");
}else{
printf("get message:%d,%s\n",n_read,readBuf);
}
write(c_fd,msg,strlen(msg));//子进程发送数据
break;
}
}
return 0;
}
client.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
int c_fd;
int n_read;
char readBuf[128];
char *msg="msg from client";
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
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);
//2.connect
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1){
perror("connect");
exit(-1);
}
//6.send
write(c_fd,msg,strlen(msg));
//5.read
n_read=read(c_fd,readBuf,128);
if(n_read ==-1){
perror("read");
}else{
printf("get message from message:%d,%s\n",n_read,readBuf);
}
return 0;
}
server1.c(相对于上面server.c加上主函数调参)
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
int main(int argc,char **argv)//main调参
{
int s_fd;
int c_fd;
int n_read;
int mark=0;//添加标志位
char readBuf[128];
// char *msg="i get your connect";
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.socket
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);
//2.bind
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.listen
listen(s_fd,10);
//4.accept
int clen=sizeof(struct sockaddr_in);
while(1){
c_fd =accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1){
perror("accept");
}
mark++;//当有客户端加入记录
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
if(fork() == 0){
//5.read
if(fork() ==0){
while(1){
sprintf(msg,"welcome NO.%d client",mark);
write(c_fd,msg,strlen(msg));
sleep(3);
}
}
while(1){
memset(readBuf,0,sizeof(readBuf));
n_read=read(c_fd,readBuf,128);
if(n_read ==-1){
perror("read");
}else{
printf("get message:%d,%s\n",n_read,readBuf);
}
}
break;
}
}
return 0;
}
client1.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
int c_fd;
int n_read;
char readBuf[128];
// char *msg="msg from client";
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
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);
//2.connect
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1){
perror("connect");
exit(-1);
}
while(1){
if(fork() == 0){
while(1){
memset(msg,0,sizeof(msg));
printf("input: ");
gets(msg);
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 message:%d,%s\n",n_read,readBuf);
}
}
}
//6.send
//5.read
return 0;
}