零基础小白学习Linux系统编程对网络编程的了解。
前面学习的Linux进程间的通信(管道、消息队列、共享内存、信号以及信号量),都是依赖于内核,只适用于单机通信,完成Linux服务器内部的协调工作。
要实现多机的通信,就需要利用地址(IP地址、端口号)、协议(TCP、UDP等)来标识网络的进程。使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX BSD的套接字(socket),来实现网络进程之间的通信。
socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。
网络编程的API以及一般步骤
socket——用来创建一个socket描述符
bind()——把特定地址(ipv4或ipv6和端口号)赋给socket
listen()——监听有无客户端的接入
accept()——接受连接请求
connect()——发出连接请求
read()、write()——读和写,用于数据交互
一、socket 函数
就像普通文件的打开函数返回一个文件描述符,通过该描述符操做读、写、关闭等操做一样,socket函数也是用来创建一个socket(套接字)描述符,来操做后面的内容。
//头文件包含
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//函数
int socket(int domain, int type, int protocol);
参数介绍:
domain——指明所使用的协议族,通常为AF_INET表示互联网协议族(TCP/IP)
type——指定socket的类型,如果domain选用AF_INET,那么type一般为SOCK_STREAM,表示使用TCP协议。
protocol——协议,通常写0
二、bind函数
把特定地址(ipv4或ipv6和端口号)赋给socket。
//头文件包含
#include <sys/types.h>
#include <sys/socket.h>
//函数
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数介绍:
sockfd——socket描述符(socket的返回值)
addr——const struct sockaddr 类型的指针,通常用struct sockaddr 类型的指针来等同替换,所以使用时要进行类型强制转换。sockaddr 、sockaddr_in 两结构体在下面具体介绍。
addrlen——结构体sockaddr(替换后为结构体sockaddr_in )的长度
struct sockaddr {
sa_family_t sa_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结构内存对齐,两者才能相互替换,无实际意义
}
此外,两个地址转换的API在此也会用到,它们是:
int inet_aton(const char * straddr,struct in_addr *addrp);
//作用:把字符串形式"192.168.42.162"的IP转换为网络能识别的格式
//straddr:IP如 "192.168.42.162"
//addrp:存放IP的地址
char *inet_ntoa(struct in_addr inaddr);
//作用:把网络格式的IP转换为字符串形式
三、listen函数
此函数只能用于服务端,可设置最大连接数。
//头文件包含
#include <sys/types.h>
#include <sys/socket.h>
//函数
int listen(int sockfd, int backlog);
参数介绍:
sockfd:socket描述符(socket的返回值)
backlog:最大连接个数
四、accept函数
用于接收客户端的接入。
//头文件包含
#include <sys/types.h>
#include <sys/socket.h>
//函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数介绍:
sockfd——socket描述符(socket的返回值)
addr——用来返回客户端IP地址
addrlen——客户端地址长度
五、connect函数
客户端使用函数,用于向服务端发出连接请求。
//头文件包含
#include <sys/types.h>
#include <sys/socket.h>
//函数
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数介绍:
sockfd——socket描述符(socket的返回值)
addr——服务端IP地址、端口号地址结构体指针
addrlen——结构体sockaddr 的长度,即sizeof(struct sockaddr)
六、代码示例
示例一:
实现服务端与一客户端简单的自动对话。
服务端代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int s_fd;
int n_read;
char *msg="I get you message!";
char readbuf[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));
//1.socket
s_fd=socket(AF_INET,SOCK_STREAM,0);
if(s_fd==-1){
perror("socket");
exit(-1);
}
//2.binf
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(8585); //htons() :字节转换,返回网络字节序的值。具体看最后面的补充。
inet_aton("172.30.24.133",&s_addr.sin_addr);
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);
int c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd==-1)
{
perror("accept");
}
//打印客户端的IP
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
//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);
}
//6.write
write(c_fd,msg,strlen(msg));
printf("\n");
return 0;
}
客户端代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int c_fd;
int n_read;
char *msg="msg from client!";
char readbuf[128]={0};
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);
}
//2.connect
c_addr.sin_family=AF_INET;
c_addr.sin_port=htons(8585);
inet_aton("172.30.24.133",&c_addr.sin_addr);
if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr))==-1){
perror("connect");
exit(-1);
}
//3.send
write(c_fd,msg,strlen(msg));
//4.read
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);
}
return 0;
}
运行结果:
示例二:
实现服务端与多个客户端的对接,服务端可以收到来自多个客户端的消息,客户端接入后便会收到来自服务端自动回复的一句话 welcom No.xxx client 。
服务端代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char **argv)
{
int s_fd;
int c_fd;
int n_read;
int mark=0;
char msg[128]={0};
char readbuf[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));
//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.binf
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 %d connect:%s\n",mark,inet_ntoa(c_addr.sin_addr));
if(fork()==0){
//5.read
memset(msg,0,sizeof(msg));
sprintf(msg,"welcom No.%d client",mark);
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 %d client:%d\t%s\n",mark,n_read,readbuf);
}
}
break;
}
}
return 0;
}
客户端代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char** argv)
{
int c_fd;
int n_read;
char msg[128]={0};
char readbuf[128]={0};
struct sockaddr_in c_addr;
//初始化结构体变量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))==-1){
perror("connect");
exit(-1);
}
while(1){
memset(readbuf,0,sizeof(readbuf));
n_read=read(c_fd,readbuf,128);
if(n_read==-1){
perror("read");
}else{
printf("%s\n",readbuf);
}
if(fork()==0){
while(1){
memset(msg,0,sizeof(msg));
printf("input: ");
gets(msg);
write(c_fd,msg,strlen(msg));
}
}
}
printf("\n");
return 0;
}
运行结果:
内容补充
1、 字节序
概述:字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。
Little endian(小端字节序):将低序字节存储在起始地址。
Big endian(大端字节序):将高序字节存储在起始地址。
网络字节序等于大端字节序;x86系列(电脑)CPU都是小端字节序。
2、TCP/UDP对比:
3、端口号作用: