网络编程
使用网络编程实现多机通讯
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时的地址协议族的不同而不同。
int inet_aton(const char* straddr, struct in_addr *addrp);
char* inet_ntoa(struct in_addr inaddr);
-
监听网络连接; #include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int 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);
-
数据交互; 在套接字通信中进行字节读取函数: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);
第一个将buf中的nbytes个字节写入到文件描述符fd中,成功返回写的字节数。第二个为从fd中读取nbyte个字节到buf中,返回实际所读的字节数。详细应用说明参数使用read,write读写socket(套节字)
网络I/O还有一些函数,例如:recv()/send(),readv()/writev(),recvmsg()/sendmsg(),recvfrom()/sendto()等。
ssize_t send(int s, const void *msg, size_t len, int flags);
ssize_t recv(int s, void *buf, size_t len, int flags);
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
关闭套接字,断开连接。
socket服务端代码实现
grep “struct sockaddr_in {” * -nir:可以找到struct sockaddr_in所在的文件
vi linux/in.h +184找到文件中所在的行数
#include <netinet/in.h>
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);
uint16_t ntohs(uint16_t net16bitvalue);
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>
#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));
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if(s_fd == -1)
{
perror("socket");
exit(0);
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8000);
inet_aton("192.168.1.120", &(s_addr.sin_addr));
bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
listen(s_fd, 10);
int clen = sizeof(struct sockaddr_in);
c_fd = accept(s_fd, (struct sockaddr *)&s_addr, &clen);
if(c_fd == -1)
{
perror("accept");
}
printf("get message:%s\n", inet_ntoa(s_addr.sin_addr));
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, message, strlen(message));
return 0;
Socket客户端代码实现
#include <stdio.h>
#include <sys/types.h>
#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));
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);
inet_aton("192.168.1.116", &(c_addr.sin_addr));
int ret = connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr));
if(ret == -1)
{
perror("connect");
exit(-1);
}
write(c_fd, c_msg, strlen(c_msg));
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>
#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));
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if(s_fd == -1)
{
perror("socket");
exit(0);
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1], &(s_addr.sin_addr));
bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
listen(s_fd, 10);
int clen = sizeof(struct sockaddr_in);
while(1)
{
c_fd = accept(s_fd, (struct sockaddr *)&s_addr, &clen);
if(c_fd == -1)
{
perror("accept");
}
printf("get message:%s\n", inet_ntoa(s_addr.sin_addr));
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);
}
}
break;
}
}
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#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));
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));
int ret = connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr));
if(ret == -1)
{
perror("connect");
exit(-1);
}
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));
}
}
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;
}
|