Linux网络编程2-socket编程

一. 网络套接字:

套接字就是socket,socket就像网络两端的两个站点,他们之间的要走的路就是数据传输的线路。套接字会标识进程的ip和端口号,其实就标识了主机和里面那个进程(程序)。网络套接字是一个文件,用文件描述符表示。
在这里插入图片描述
在网络通信中,套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。我们使用同一个文件描述符索发送缓冲区和接收缓冲区。

二. 数据转化

1. 网络字节序
小端法:(pc本地存储) 高位存高地址。地位存低地址。 int a = 0x12345678大端法:(网络存储) 高位存低地址。地位存高地址。所以我们要将网络本地字节序转换为网络字节序。
常见的函数有:
1.字节序转换函数:

1)htonl()
定义:
#include <winsock.h>
u_short PASCAL FAR htons( u_short hostshort);
功能:将本地字节序转化为网络字节序
参数:16位数从主机字节顺
返回值:网络字节顺序的值
(2)htons --> 本地--》网络 (port)3)ntohl --> 网络--》 本地(IP)
(4)ntohs --> 网络--》 本地(Port)
三个函数类似
h表示host,n表示network,l表示32位长整数,s表示16位短整数。

2.IP地址转化函数:

inet_pton()函数:
定义:
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
作用:将点分十进制的IP地址转化为网络字节序的IP地址
参数:
		af:AF_INET、AF_INET6Z(那种协议ipv6-ipv4)
		src:传入,IP地址(点分十进制)
		dst:传出,转换后的 网络字节序的 IP地址。 
返回值:
返回值:
		成功: 1
		异常: 0, 说明src指向的不是一个有效的ip地址。
		失败:-1
		
inet_ntop()函数
 const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);网络字节序 ---> 本地字节序(string IP)
 参数:
		af:AF_INET、AF_INET6
		src: 网络字节序IP地址
		dst:本地字节序(string IP)
		size: dst 的大小。
返回值: 
        成功:dst。 	
        失败:NULL

三. sockaddr数据结构

在这里插入图片描述
strcut sockaddr 很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结构体,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,然后函数内部再强制类型转化为所需的地址类型。注意bind等函数使用的时第一种结构体,使用时要强制转换。两种结构体的命名如下:

struct sockaddr {
	sa_family_t sa_family; 		/* address family, AF_xxx */
	char sa_data[14];			/* 14 bytes of protocol address */
};

struct sockaddr_in {
	__kernel_sa_family_t sin_family; /* Address family地址结构类型 ipv4和ipv6 */  	
	__be16 sin_port;			/* Port number 端口号*/		
	struct in_addr sin_addr;/* Internet addressIP地址 */	
	/* Pad to size of `struct sockaddr'. */
	unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
	sizeof(unsigned short int) - sizeof(struct in_addr)];
};

例子:初始化一个sockaddr地址结构体

struct sockaddr_in addr;
addr.sin_family = AF_INET/AF_INET6	//指定ipv4-ipv6
addr.sin_port = htons(9527);        //端口
int dst;
inet_pton(AF_INET, "192.157.22.45", (void *)&dst);  
addr.sin_addr.s_addr = dst;

四. socket模型与常用函数

在这里插入图片描述
服务器端:建立socket,绑定端口和ip,设置监听上限,阻塞等待客户连接,连接成功返回一个新的能与客户端连接的socket注意这个套接字不是原来的套接字。原来的套接字只用来监听。
客户端:建立socket,建立连接。相互建立连接后开始进行数据传输。重要的函数介绍如下:
(1)socket函数:

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
作用:创建一个套接字
参数:domain:AF_INET、AF_INET6、AF_UNIX//协议类型
     type:SOCK_STREAM、SOCK_DGRAM //SOCK_STREAM可靠,基于字节流如tcp;不可靠,无连接如udp
	 protocol: 0   //使用上一个参数最常用的默认协议tcp/udp
返回值:
	成功: 新套接字所对应文件描述符
	失败: -1 errno
	

(2)bind函数

#include <arpa/inet.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);		
作用:给socket绑定一个地址结构 (一般是自己的IP+port)
参数:sockfd: 上面socket 函数返回值
const struct sockaddr *addr:就是一个套接字的数据结构
实例:
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(8888);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr: 传入参数(struct sockaddr *)&addr  //本地的端口和ip
addrlen: sizeof(addr)  //地址结构的大小。
返回值:
	成功:0
	失败:-1 errno
	

设置地址类型为AF_INET,网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址,端口号为8888。
(3)listen函数

int listen(int sockfd, int backlog);		
作用:设置同时与服务器建立连接的上限数。(同时进行3次握手的客户端数量)
参数:sockfd: 上面的socket 函数返回值
      backlog:上限数值。最大值 128.
返回值:
		成功:0
		失败:-1 errno	

(4)accept函数

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);	
作用:阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket文件描述符。
参数:sockfd: socket 函数返回值
	addr:传出参数。成功与服务器建立连接的那个客户端的地址结构(			IP+port)
	socklen_t clit_addr_len = sizeof(addr);
	addrlen:传入传出。 &clit_addr_len
			入:addr的大小。 
			出:客户端addr实际大小。
返回值:
	成功:能与客户端进行数据通信的 socket 对应的文件描述。
	失败: -1 , errno

(5)connect函数

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);	  
作用:使用现有的 socket 与服务器建立连接
参数:sockfd: socket 函数返回值
	 struct sockaddr_in srv_addr;	服务器地址结构
	 srv_addr.sin_family = AF_INET;
	 srv_addr.sin_port = 9527 	跟服务器bind时设定的 port 完全一致。
     inet_pton(AF_INET, "服务器的IP地址"&srv_adrr.sin_addr.s_addr);
     addr:传入参数。服务器的地址结构
     addrlen:服务器的地址结构的大小
返回值:
	成功:0
	失败:-1 errno

代码示例:客户和服务器端实现大小写转换:
serve.c

1.	#include <stdio.h>  
2.	#include <ctype.h>  
3.	#include <sys/socket.h>  
4.	#include <arpa/inet.h>  
5.	#include <stdlib.h>  
6.	#include <string.h>  
7.	#include <unistd.h>  
8.	#include <errno.h>  
9.	#include <pthread.h>  
10.	  
11.	#define SERV_PORT 9527  
12.	  
13.	  
14.	void sys_err(const char *str)  
15.	{  
16.	    perror(str);  
17.	    exit(1);  
18.	}  
19.	  
20.	int main(int argc, char *argv[])  
21.	{  
22.	    int lfd = 0, cfd = 0;  
23.	    int ret, i;  
24.	    char buf[BUFSIZ], client_IP[1024];  
25.	  
26.	    struct sockaddr_in serv_addr, clit_addr;  // 定义服务器地址结构 和 客户端地址结构  
27.	    socklen_t clit_addr_len;                  // 客户端地址结构大小  
28.	  
29.	    serv_addr.sin_family = AF_INET;             // IPv4  
30.	    serv_addr.sin_port = htons(SERV_PORT);      // 转为网络字节序的 端口号  
31.	    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 获取本机任意有效IP  
32.	  
33.	    lfd = socket(AF_INET, SOCK_STREAM, 0);      //创建一个 socket  
34.	    if (lfd == -1) {  
35.	        sys_err("socket error");  
36.	    }  
37.	  
38.	    bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));//给服务器socket绑定地址结构(IP+port)  
39.	  
40.	    listen(lfd, 128);                   //  设置监听上限  
41.	  
42.	    clit_addr_len = sizeof(clit_addr);  //  获取客户端地址结构大小  
43.	  
44.	    cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);   // 阻塞等待客户端连接请求 
45.	 	if (cfd == -1)  
46.	        sys_err("accept error");  
47.	  
48.	    printf("client ip:%s port:%d\n",   
49.	            inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, client_IP, sizeof(client_IP)),   
50.	            ntohs(clit_addr.sin_port));         // 根据accept传出参数,获取客户端 ip 和 port  
51.	  
52.	    while (1) {  
53.	        ret = read(cfd, buf, sizeof(buf));      // 读客户端数据  
54.	        write(STDOUT_FILENO, buf, ret);         // 写到屏幕查看  
55.	  
56.	        for (i = 0; i < ret; i++)                // 小写 -- 大写  
57.	            buf[i] = toupper(buf[i]);  
58.	  
59.	        write(cfd, buf, ret);                   // 将大写,写回给客户端。  
60.	    }  
61.	  
62.	    close(lfd);  
63.	    close(cfd);  
64.	  
65.	    return 0;  
66.	}  

服务器端编译测试:
在这里插入图片描述

cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);

accept函数中的clit_addr传出的就是客户端地址结构,IP+port
于是,在代码中增加此段代码,可获取客户端信息:

printf("client ip:%s port:%d\n", inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr, client_IP, sizeof(client_IP)),  ntohs(clit_addr.sin_port));

client.c

1.	#include <stdio.h>  
2.	#include <sys/socket.h>  
3.	#include <arpa/inet.h>  
4.	#include <stdlib.h>  
5.	#include <string.h>  
6.	#include <unistd.h>  
7.	#include <errno.h>  
8.	#include <pthread.h>  
9.	  
10.	#define SERV_PORT 9527  
11.	  
12.	void sys_err(const char *str)  
13.	{  
14.	    perror(str);  
15.	    exit(1);  
16.	}  
17.	  
18.	int main(int argc, char *argv[])  
19.	{  
20.	    int cfd;  
21.	    int conter = 10;  
22.	    char buf[BUFSIZ];  
23.	      
24.	    struct sockaddr_in serv_addr;          //服务器地址结构  
25.	  
26.	    serv_addr.sin_family = AF_INET;  
27.	    serv_addr.sin_port = htons(SERV_PORT);  
28.	    //inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);  
29.	    inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);  
30.	  
31.	    cfd = socket(AF_INET, SOCK_STREAM, 0);  
32.	    if (cfd == -1)  
33.	        sys_err("socket error");  
34.	  
35.	    int ret = connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));  
36.	    if (ret != 0)  
37.	        sys_err("connect err");  
38.	  
39.	    while (--conter) {  
40.	        write(cfd, "hello\n", 6);  
41.	        ret = read(cfd, buf, sizeof(buf));  
42.	        write(STDOUT_FILENO, buf, ret);  
43.	        sleep(1);  
44.	    }  
45.	  
46.	    close(cfd);  
47.	  
48.	    return 0;  
49.	}  

编译运行,结果如下:
在这里插入图片描述
这里遇到过一个问题,如果之前运行server,用Ctrl+z终止进程,ps aux列表里会有服务器进程残留,这个会影响当前服务器。解决方法是kill掉这些服务器进程。不然端口被占用,当前运行的服务器进程接收不到东西,没有回显。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值