Linux网络编程之Socket编程,socket、bind、listen、accept、字节序转换,TCP与UDP的区别

本文详细介绍了Linux下Socket编程,包括TCP与UDP的对比、端口号的作用、字节序转换,以及Socket编程步骤。针对服务端和客户端,详细解释了创建套接字、绑定、监听、接受连接、数据收发等关键步骤,并提供了实例代码。

目录

1、TCP与UDP的对比:

2、什么是端口号

3、端口号的作用

4、什么是字节序

(1)主机字节序转网络字节序

(2)网络字节序转主机字节序 

(3)将字符串IP地址转换为网络字节序的整型数据

(4)将网络字节序数据转换为主机字符串IP地址 

(5)网络字节序IP和主机字节序IP转换其它API 

5、Socket编程步骤

服务端

1、创建套接字:socket()

2、为套接字添加信息(IP地址和端口号):bind()

3、地址转换API

4、监听网络连接:listen()

5、监听到有客户端接入,开始接收连接:accept()

6、数据收发

        Socket服务端代码实现 

客户端

1、创建套接字:socket()

2、连接服务器(主机):connect()

        Socket客户端代码实现 

举个例子:用Socket实现双方聊天 

        Socket实现双方聊天——服务端代码

        Socket实现双方聊天——客户端代码

6、怎么在Linux中找头文件


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地址找到对应的服务器端,但是服务器端是有很多端口的,每个应用程序对应一个端口号,通过类似门牌号的端口号,客户端才能真正的访问到该服务器。为了对端口进行区分,将每个端口进行了编号,这就是端口号。

3、端口号的作用

在一台计算机上同时可以进行多个应用程序,例如接受www服务的Web浏览器、电邮客户端、远程登录用的ssh客户端等程序都可以同时进行。传输层协议正是利用这些端口号识别本机中正在进行通信的应用程序,并精准的将数据传输。

4、什么是字节序

字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序

大端字节序:高字节存放在低地址,低字节存放在高地址

小端字节序:低字节存放在低地址,高字节存放在高地址

网络字节序 = 大端字节序

 

(1)主机字节序转网络字节序

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);        无符号的32位整型数据的转换 
int16_t htons(uint16_t hostshort);        无符号的16位整型数据的转换 

功能:htons和htonl都是实现将主机字节序数据转换为网络字节序数据
参数:hostlong和hostshort标识主机字节序数据
返回值:网络字节序数据

(2)网络字节序转主机字节序 

#include <arpa/inet.h>
uint32_t ntohl(uint32_t netlong);        无符号的32位整型数据的转换
uint16_t ntohs(uint16_t netshort);       无符号的16位整型数据的转换 

参数:netlong和netshort标识网络字节序数据
返回值:主机字节序数据

(3)将字符串IP地址转换为网络字节序的整型数据

#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);

    参数1:af表示地址协议族
        AF_INET:    基于IPV4协议族的IP地址
        AF_INET6:   基于IPV6协议族的IP地址
    参数2:src指针指向的字符串IP地址;
    参数3:det目标指针用来返回转换接收的整型数据(网络字节序的IP地址);
返回值:
    装换成功返回1;af指定地址协议族中没有找到IP则返回0,否则失败返回-1
    

(4)将网络字节序数据转换为主机字符串IP地址 

#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

    参数1:af表示地址协议族
        AF_INET:    基于IPV4协议族的IP地址
        AF_INET6:   基于IPV6协议族的IP地址
    参数2:传递网络字节序IP地址的整型数据存储空间的地址;
    参数3:返回主机字节序IP地址,
    参数4:表示参数3所对对应空间的大小
        size可以使用宏定义:
            INET_ADDRSTRLEN    //ipv4空间大小;
            INET6_ADDRSTRLEN   //ipv6空间大小;
返回值:成功返回dst的指针,失败返回NULL且修改errno的值。

(5)网络字节序IP和主机字节序IP转换其它API 

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);        
功能: 将cp指向的主机字节序IP地址转换为网络字节序的IP地址存储在结构体指针inp指向的空间中

in_addr_t inet_addr(const char *cp);
功能:将cp指向的主机字节序IP地址转换为网络字节序的IP地址通过函数的返回值返回 

in_addr_t inet_network(const char *cp);
功能:将cp指向的主机字节序IP地址转换为网络字节序的IP地址通过函数的返回值返回   

char *inet_ntoa(struct in_addr in);
功能:将网络字节序IP地址转为主机字节序的字符串IP地址通过返回值返回。  

struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host);

in_addr_t inet_lnaof(struct in_addr in);

in_addr_t inet_netof(struct in_addr in);

5、Socket编程步骤

服务端

1、创建套接字:socket()

#include <sys/types.h>         
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

domain:  地址族,常用有AF_INET和AF_INET6,分别代表IPv4和IPv6
type:    套接字类型,常用SOCK_STREAM(流格式套接字/面向连接的TCP套接字), 
          SOCK_DGRAM(数据报套接字/无连接的UDP套接字)

protocol:默认写0,常用IPPROTO_TCP,IPPROTO_UDP,分贝代表TCP和UDP协议

int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);  创建TCP套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);   创建UDP套接字

2、为套接字添加信息(IP地址和端口号):bind()

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

int sockfd:                    是socket描述符
const struct sockaddr *addr:   是一个结构体指针
socklen_t addrlen:             是第二个参数,结构体指针的大小,一般用sizeof()就可以搞定


addr结构体的原型是:
struct sockaddr_in {
                
               sa_family_t      sa_family;    协议族
               in_port_t        sin_port;     端口号
               struct in_addr   sin_addr;     IP地址结构体
               unsigned char    sin_zero[8];  填充,没有实际意义,只是为跟sockaddr结构在内存 
                                              中对齐,这样两者才能相互转换
           }

3、地址转换API

把字符串形式的"192.168.1.123"转为网络能识别的格式
int inet_aton(const char* straddr,struct in_addr* addrp);

const char* straddr:       字符串
struct in_addr* addrp:     一个结构体,IP地址

把网络格式的IP地址转为字符串形式
char* inet_ntoa(struct in_addr inaddr);

struct in_addr inaddr:     一个结构体,IP地址

4、监听网络连接:listen()

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);

int sockfd:   socket的描述符
int backlog:  支持的最大连接数,一个整数

5、监听到有客户端接入,开始接收连接:accept()

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

int sockfd:              socket描述符
struct sockaddr *addr:   客户端的协议地址,一个结构体,如果不关心,传NULL
socklen_t *addrlen:      客户端地址长度,如果不关心,传NULL

6、数据收发

数据收发常用第一套API
ssize_t write(int fd, const void *buf, size_t count);  
ssize_t read(int fd, void *buf, size_t count);         

数据收发常用第二套API:有连接
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

        Socket服务端代码实现 

#include <stdio.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(){

	int s_fd;
    int c_fd;
	int n_read;
	char readBuf[128];
	char *msg = "I get your connect";

	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){
		printf("socket create fail\n");
	}

	s_addr.sin_family = AF_INET;        //添加套接字数据类型:TCP协议数据类型
	s_addr.sin_port = htons(8892);      //添加套接字端口号,并且把主机端口号转换到网络端口号
	inet_aton("192.168.101.217",&s_addr.sin_addr);     //主机字符串转换到网络字符串

	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));  //为套接字添加信息
	listen(s_fd,10);                    //监听客户端连接

	int clen = sizeof(struct sockaddr);
	c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);      //开始接收客户端连接
	if(c_fd == -1){
		perror("accept");
	}

	printf("get connect: %s\n",inet_ntoa(c_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,msg,strlen(msg));                             //向客户端发送信息

	return 0;
}

执行结果:

客户端

1、创建套接字:socket()

#include <sys/types.h>         
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

domain:  地址族,常用有AF_INET和AF_INET6,分别代表IPv4和IPv6
type:    套接字类型,常用SOCK_STREAM(流格式套接字/面向连接的TCP套接字), 
          SOCK_DGRAM(数据报套接字/无连接的UDP套接字)

protocol:默认写0,常用IPPROTO_TCP,IPPROTO_UDP,分贝代表TCP和UDP协议

int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);  创建TCP套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);   创建UDP套接字

2、连接服务器(主机):connect()

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

int sockfd:                    客户端的socket描述符
const struct sockaddr *addr:   客户端服务器的IP地址和端口号,一个结构体
socklen_t addrlen:             是第二个参数,结构体指针的大小,一般用sizeof()就可以搞定

        Socket客户端代码实现 

#include <stdio.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(){

	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){
		printf("socket create fail\n");
	}

	c_addr.sin_family = AF_INET;
	c_addr.sin_port = htons(8892);
	inet_aton("192.168.101.217",&c_addr.sin_addr);

	//2.connect  ---->连接服务器(主机)
	if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1){ 
		perror("connect");
        exit(-1);
	}
	//3.write    ---->向服务器发送信息
	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;
}

执行结果:

举个例子:用Socket实现双方聊天 

        Socket实现双方聊天——服务端代码

#include <stdio.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc,char **argv){

	int s_fd;
	int c_fd;
	int n_read;
	char readBuf[128];
	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));

	s_fd = socket(AF_INET,SOCK_STREAM,0);
	if(s_fd == -1){
		printf("socket create fail\n");
	}

	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));
	listen(s_fd,10);

	int clen = sizeof(struct sockaddr);
	while(1){
		c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
		if(c_fd == -1){
			perror("accept");
		}
		printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
		if(fork() == 0){
			
			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 %d,%s\n",n_read,readBuf);
				}	
			}
		
		}
	}
	return 0;
}

        Socket实现双方聊天——客户端代码

#include <stdio.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc,char **argv){

	int c_fd;
	int n_read;
	char readBuf[128];
	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){
		printf("socket create fail\n");
	}

	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");
	}
	while(1){

		if(fork() == 0){
			//3.write
			while(1){
				memset(msg,0,sizeof(msg));
				printf("input: ");
				gets(msg);
				write(c_fd,msg,strlen(msg));
			}
		}
		//4.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 from server %d,%s\n",n_read,readBuf);
			}
		}
	}
	return 0;
}

双方聊天代码执行结果:

6、怎么在Linux中找头文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值