网络编程
使用网络编程实现多机通讯
TCP/UDP对比
- TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。
- TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
- TCP面向字节流,实际上是TCP把数据看成一串无结构的字节流;UDP是面向报文的,UDP没有阻塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)。
- 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
- TCP首部开销20字节;UDP首部开销小,只有8个字节。
- TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。
端口号作用
一台拥有IP地址的主机可以提供许多服务,如Web服务,FTP服务,SMTP服务等。
实际上通过“IP地址+端口号”来区分不同的服务。端口提供一种访问通道,服务器一般都是通过知名端口号来识别。如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69。
字节序
字节序是指多字节数据在计算机内存中存储或网络传输时各字节的存储顺序。
常见序:Little endian(小端字节序):将低序字节存储在起始地址;Big endian(大端字节序):将高序字节存储在起始地址。
Socket服务器和客户端的开发步骤
-
创建套接字;
int socket(int domain, int type, int protocol); domain:指明所使用的协议族,通常用AF_INET,表示互联网协议族(TCP/IP协议族); AF_INET IPv4因特网域 AF_INET6 IPv6因特网域 AF_UNIX Unix域 AF_ROUTE 路由套接字 AF_KEY 密钥套接字 AF_UNSPEC 未指定 type参数指定socket的类型: SOCK_STREAM: 流式套接字提供可靠的,面向连接的通信流;它使用TCP协议,从而保证数据传输的 正确性和顺序性 SOCK_DGRAM: 数据报套接字定义了一种无连接的服,数据通过相互独立的报文进行传输的,是无序 的,并且不保证是可靠的,无差错的。它使用数据报协议UDP。 SOCK_RAM: 允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功 能强大但使用较为不方便,主要用于一些协议的开发。 protocol:通常赋值“0” 0 选择type类型对应的默认协议 IPPROTO_TCP TCP传输协议 IPPROTO_UDP UDP传输协议 IPPROTO_SCTP SCTP传输协议 IPPROTO_TIPC TIPC传输协议
-
为套接字添加信息(IP地址和端口号);
bind()函数:IP号端口号与相应描述字赋值函数
#include <sys/types.h> #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); //ipv4对应的是: struct sockaddr{ unsigned short as_family;//协议族 char sa_data[14]; //IP+端口号 }; //同等替换 struct sockaddr_in{ sa_family_t sin_family;//协议族 in_port_t sin_port;//端口号 struct in_addr sin_addr;//IP地址结构体 unsigned char sin_zero[8];//填充 没有实际意义,只是跟sockaddr结构在内存中对齐,这样两者才能相互交换 };
功能:
用于绑定IP地址和端口号到socketfd;
参数:
sockfd:是一个socket描述符
addr:是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族的不同而不同。
//地址转换API int inet_aton(const char* straddr, struct in_addr *addrp);//把字符串形式的“192.168.1.123”转换为网络能识别的格式 char* inet_ntoa(struct in_addr inaddr);//把网络格式的ip地址转为字符串形式
-
监听网络连接;
#include <sys/types.h> #include <sys/socket.h> int listen(int sockfd, int backlog);//sockfd是socket系统调用返回的服务器端socket描述符;backlog指定在请求队列中允许的最大请求数
功能:
设置能处理的最大连接数,listen()并未开始接受连线,只是设置socket的listen模式,listen函数只用于服务器端,服务器进程不知道要与谁连接,因此,他不会主动的要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对他作出处理,一个服务进程可以同时处理多个客户进程的连接。主要就两个功能:将一个未连接的套接字转换为一个被动套接字(监听),规定内核为相应套接字排队的最大连接数。
内核为任何一个给定监听套接字维护两个队列:
未完成连接队列,每个这样的SYN报文段对应其中一项:已由某个客户端发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程。这些套接字处于SYN_REND状态;
已经完成连接队列,每个已完成TCP三次握手过程的客户端对应其中一项。这些套接字处于ESTABLISHED状态
-
监听到有客户端接入,接受一个连接;
#include <sys/types.h> #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//sockfd是socket系统调用返回的服务器端socket描述符;addr用来返回已连接的对端(客户端)的协议地址;addrled客户端地址长度。 //功能:accept函数由TCP服务器调用,用于从已经完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠 //返回值:该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的声明周期内一直存在。内核为每个由服务器进程接受的客户端创建一个已连接套接字(表示TCP三次握手已完成),当服务器完成对某个给定客户的服务器时,相应的已连接套接字就会被关闭。
-
数据交互;
在套接字通信中进行字节读取函数:read(),write()。与I/O中的读取函数略有区别,因为他们输入或输出的字节数比可能比请求的少。
ssize_t write(int fd, const void *buf, size_t nbytes); ssize_t read(int fd, void *buf, size_t nbyte);//函数均返回读或写的字节个数,出错返回-1 第一个将buf中的nbytes个字节写入到文件描述符fd中,成功返回写的字节数。第二个为从fd中读取nbyte个字节到buf中,返回实际所读的字节数。详细应用说明参数使用read,write读写socket(套节字) 网络I/O还有一些函数,例如:recv()/send(),readv()/writev(),recvmsg()/sendmsg(),recvfrom()/sendto()等。
//在TCP套接字上发送数据函数:有连接 ssize_t send(int s, const void *msg, size_t len, int flags); /* 包含三个要素:套接字s,待发数据msg,数据长度len 函数只能对处于连接状态的套接字使用,参数s为已建立好连接的套接字描述符,即accept函数的返回值 参数msg指向存放待发送数据的缓冲区 参数len为待发送数据的长度,参数flags为控制选项,一般设置为0 */ //在TCP套接字上接收数据函数:有连接 ssize_t recv(int s, void *buf, size_t len, int flags); /* 包含三个要素:套接字s,接收缓冲去buf,数据长度len 函数recv从参数s所指定的套接字描述符(必须是面向连接的套接字)上接收数据并保存到参数buf所指定的缓冲区 参数len为缓冲区长度,参数flags为控 制选项,一般设置为0 */
//客户端连接函数 #include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//sockfd是目的服务器的socket描述符;addr是服务器端IP地址和端口号的地址结构指针;addrled地址长度常被设置为sizeof(struct sockaddr) /* 功能:该函数用于绑定之后的client端(客户端),与服务器建立连接 返回值:成功返回0;错误返回-1,并且errno中包含相应的错误码 */
-
关闭套接字,断开连接。
socket服务端代码实现
grep “struct sockaddr_in {” * -nir:可以找到struct sockaddr_in所在的文件
vi linux/in.h +184找到文件中所在的行数
//字节序转换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 host32bitvalue);//返回主机字节序的值
h代表host,n代表net,s代表short(两个字节),l代表long(4个字节),通过上边的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取。
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int s_fd;
int c_fd;
int n_read;
char readBuf[128];
char *message = "I get your meaasge";
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));
//socket()
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if(s_fd == -1)
{
perror("socket");
exit(0);
}
//bind()
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8000); //5000-9000
inet_aton("192.168.1.120", &(s_addr.sin_addr));
bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
//listen()
listen(s_fd, 10);
//accept()
int clen = sizeof(struct sockaddr_in);
c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen);
if(c_fd == -1)
{
perror("accept");
}
printf("get message:%s\n", inet_ntoa(s_addr.sin_addr));
//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()
write(c_fd, message, strlen(message));
return 0;
Socket客户端代码实现
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int c_fd;
int n_read;
char readBuf[128];
char *c_msg = "meaasge from client";
struct sockaddr_in c_addr;
memset(&c_addr, 0, sizeof(struct sockaddr_in));
//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(8000); //5000-9000
inet_aton("192.168.1.116", &(c_addr.sin_addr));
//connect
int ret = connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr));
if(ret == -1)
{
perror("connect");
exit(-1);
}
//send
write(c_fd, c_msg, strlen(c_msg));
//read()
n_read = read(c_fd, readBuf, 128);
if(n_read == -1)
{
perror("read");
}
else
{
printf("get message:%d, %s\n", n_read, readBuf);
}
return 0;
}
实现双方聊天
格式化代码:
- gg 回到文件的第一行
- shift+v
- shift+g 回到文件的最后一行
- = 格式化
/***************************服务器******************************/
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
int s_fd;
int c_fd;
int n_read;
char readBuf[128];
char message[128] = {0};
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));
//socket()
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if(s_fd == -1)
{
perror("socket");
exit(0);
}
//bind()
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2])); //5000-9000
inet_aton(argv[1], &(s_addr.sin_addr));
bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
//listen()
listen(s_fd, 10);
//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");
}
printf("get message:%s\n", inet_ntoa(s_addr.sin_addr));
//read()
if(fork() == 0)
{
if(fork() == 0)
{
while(1)
{
memset(message, 0, sizeof(message));
printf("Input:");
gets(message);
write(c_fd, message, strlen(message));
}
}
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);
}
}
//write()
break;
}
}
return 0;
}
/******************************客户端***************************/
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
int c_fd;
int n_read;
char readBuf[128];
char c_msg[128] = {0};
struct sockaddr_in c_addr;
memset(&c_addr, 0, sizeof(struct sockaddr_in));
//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])); //5000-9000
inet_aton(argv[1], &(c_addr.sin_addr));
//connect
int ret = connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr));
if(ret == -1)
{
perror("connect");
exit(-1);
}
//send
while(1)
{
if(fork() == 0)
{
while(1)
{
memset(c_msg, 0, sizeof(c_msg));
printf("Input:");
gets(c_msg);
write(c_fd, c_msg, strlen(c_msg));
}
}
//read()
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);
}
}
}
return 0;
}