Socket编程

目录

1.建立连接

socket函数:

 bind函数:

listen函数:

 accept函数:

 connect函数:

2.发送和接收

3.辅助性函数

4.IP地址转换函数:

5.表示IP地址相关数据结构

6.socket编程实践

1.服务器端程序编写

2.客户端程序的编写

3.运行效果:

7.出错处理封装函数

wrap.c

wrap.h


1.建立连接

(1)socket函数:建立一个套接口,类似于open,用来打开一个网络连接,如果成功则返回一个网络文件描述符(int),之后我们操作这个网络连接都通过这个网络文件描述符。

(2)bind函数:将socket建立的套接口与一个本地地址捆绑(主机地址/端口号)。

(3)listen函数:把一个未连接的套接字转换成一个被动套接字,指示内核应该接受指向该套接字的连接请求。

(4)accept函数:开始接收从客户端发来的请求信息。

(5)connect函数:发起对服务器的连接请求,三次握手在此时开始。

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

        protocol: 0

返回值:

        成功: 新套接字所对应文件描述符

        失败: -1 errno

 bind函数:

#include <arpa/inet.h>

 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);  //给socket绑定一个 地址结构 (IP+port)

        sockfd: socket 函数返回值

        addr: 传入参数(struct sockaddr *)&addr

                struct sockaddr_in addr;

                        addr.sin_family = AF_INET;

                        addr.sin_port = htons(8888);

                        addr.sin_addr.s_addr =inet_addr(SER_ADDR_IP);

        addrlen: sizeof(addr) 地址结构的大小。

返回值:

        成功:0

        失败:-1 errno

listen函数:

int listen(int sockfd, int backlog); //设置同时与服务器建立连接的上限数。(同时进行3次握手的客户端数量)

        sockfd: socket 函数返回值

        backlog:上限数值。最大值 128.

返回值:

        成功:0

        失败:-1 errno

 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

                        传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小

返回值:

        成功:能与客户端进行数据通信的 socket 对应的文件描述。

        失败: -1 , errno

 connect函数:

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);   使用现有的 socket 与服务器建立连接

        sockfd: socket 函数返回值

        addr:传入参数。服务器的地址结构

                struct sockaddr_in srv_addr; // 服务器地址结构

                        srv_addr.sin_family = AF_INET;

                        srv_addr.sin_port = 9527          //跟服务器bind时设定的 port 完全一致。

                       ser_sockaddr.sin_addr.s_addr = inet_addr(SER_ADDR_IP);   // 服务器IP地址

         addrlen:服务器的地址结构的大小

返回值:

        成功:0

        失败:-1 errno

2.发送和接收

(1)send和write:都是用来发送信息。

(2)recv和read:都是用来接收信息。

3.辅助性函数

(1)inet_aton(将字符串转换成网络地址)、inet_addr(构建网络地址)、inet_ntoa(将网络地址转换成字符串)。

(2)inet_ntop(将网络地址转换成字符串)、inet_pton(将字符串转换成网络地址)。两者都兼容IPv4和IPv6。

网络字节序:

小端法:(pc本地存储) 高位存高地址。地位存低地址。 int a = 0x12345678

大端法:(网络存储) 高位存低地址。地位存高地址。

htonl --> 本地--》网络 (IP)

h表示host,n表示network,l表示32位长整数,s表示16位短整数。

192.168.1.11 --> string --> atoi --> int --> htonl --> 网络字节序

htons --> 本地--》网络 (port)

ntohl --> 网络--》 本地(IP)

ntohs --> 网络--》 本地(Port)

4.IP地址转换函数:

int inet_pton(int af, const char *src, void *dst); 本地字节序(string IP) ---> 网络字节序

        af:AF_INET、AF_INET6

        src:传入,IP地址(点分十进制)

        dst:传出,转换后的 网络字节序的 IP地址。

返回值:

        成功: 1

        异常: 0, 说明src指向的不是一个有效的ip地址。

        失败:-1

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

5.表示IP地址相关数据结构

(1)都定义在netinet/in.h。

(2)struct sockaddr这个结构体是网络编程中用来表示一个IP地址的,注意这个IP地址是兼容IPv4和IPv6的,在实际编程中这个结构体会被struct sockaddr_in或者struct sockaddr_in6。

(3)typedef uint32_t in_addr_t:网络内部用来表示IP地址的类型。

(4)struct  sockaddr_in 

        {

                short int  sin_family;  

                unsigned short int  sin_port;

                struct in_addr  sin_addr;

                unsigned char  sin_zero[8];

        };

sin_family: 地址族。一般来说是AF_INET和PF_INET

sin_port: 端口号(使用网络字节顺序)。在linux 下,端口号的范围是 0~65535, 0~1024 范围的端口号已经被系统使用或保留。

sin_addr: 存储IP地址,使用in_addr 这个数据结构。

         typedef uint32_t in_addr_t;

          struct in_addr

         {

                in_addr_t s_addr;

        };                                                                                                                                                 

        sin_zero: 为了将 sockaddr_in 结构与 sockaddr 结构对齐

sockaddr地址结构: IP + port --> 在网络环境中唯一标识一个进程。

struct sockaddr_in  addr;

        addr.sin_family = AF_INET/AF_INET6 man 7 ip

        addr.sin_port = htons(9527);

        int dst;

        inet_pton(AF_INET, "192.157.22.45", (void *)&dst);

        addr.sin_addr.s_addr = dst;

【*】addr.sin_addr.s_addr = htonl(INADDR_ANY); 取出系统中有效的任意IP地址。二进制类型。

bind(fd, (struct sockaddr *)&addr, size);

6.socket编程实践

1.服务器端程序编写

(1)socket。

(2)bind。

(3)listen。

(4)accept:返回值是一个fd,accept正确返回表示已经与客户端建立TCP连接,之后需要通过这个连接对应的fd来和客户端进行读写操作。

注意:socket返回的fd叫监听fd,是用来监听客户端的,不能读写;accept返回的fd叫连接fd,用来读写。

myServer.c

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

#define  SER_PORT  8889			//本地端口
#define  SER_ADDR_IP  "192.168.0.15"	//本地IP

int main()
{
	int s_fd = 0 , c_fd = 0 , ret = 0;		   //文件描述符
	struct sockaddr_in ser_sockaddr = {0};     //服务器 sockaddr数据结构
	struct sockaddr_in cli_sockaddr = {0};     //客户端 sockaddr数据结构
	socklen_t cli_addr_len = 0;				   //客户端地址结构大小
	char cli_addr_ip[1024] = {0};			   //客户端IP地址
	char buf[1024] = {0};			   		   
	
	//第1步: 创建socket , 得到监听fd
	s_fd = socket(AF_INET , SOCK_STREAM , 0);
	if(-1 == s_fd)
	{
		perror("socket");
		return -1;
	}
	printf("socket = %d\n" , s_fd);
	
	//第2步: bind绑定s_fd和当前电脑的ip地址和端口号
	ser_sockaddr.sin_family = AF_INET;					// 设置地址族为IPv4
	ser_sockaddr.sin_port = htons(SER_PORT);			// 设置地址的端口号信息:本地--》网络 (IP)
	ser_sockaddr.sin_addr.s_addr = inet_addr(SER_ADDR_IP);	// 设置IP地址
	//ser_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);	// 获取本机任意有效IP
	
	ret = bind(s_fd , (const struct sockaddr *)&ser_sockaddr, sizeof(ser_sockaddr));
	if(-1 == ret)
	{
		perror("bind");
		return -1;
	}
	printf("bind success!\n");
	
	//第三步:listen监听端口
	ret = listen(s_fd , 100);
	if(-1 == ret)
	{
		perror("listen");
		return -1;
	}
	printf("listen success!\n");
	
	// 第四步:accept阻塞等待客户端接入
	cli_addr_len = sizeof(cli_sockaddr);	//获取客户端地址结构大小
	c_fd = accept(s_fd , (struct sockaddr *)&cli_sockaddr , &cli_addr_len);
	printf("连接已建立,client fd = %d\n" , c_fd);
	
	//网络字节序 ---> 本地字节序(string IP)
	printf("client ip:%s port:%d\n", 
            inet_ntop(AF_INET, &cli_sockaddr.sin_addr.s_addr, cli_addr_ip, sizeof(cli_addr_ip)), 
            ntohs(cli_sockaddr.sin_port));			// 根据accept传出参数,获取客户端 ip 和 port
	
	//小写 --> 大写 or 大写 --> 小写
	while(1)
	{
		memset(buf , 0 , sizeof(buf));
		ret = read(c_fd , buf , sizeof(buf));  //阻塞读取
		if(ret == 0) break;  	//客户端断开连接
		write(STDOUT_FILENO , buf , ret);
		
		for(int i = 0 ; i < ret ; i++)
		{
			
			if(buf[i] >= 'a' && buf[i] <= 'z')
			{
				buf[i] -= 32 ;
			}
			else if(buf[i] >= 'A' && buf[i] <= 'Z')
			{
				buf[i] += 32 ;
			}
		}
		printf("\n");
		write(c_fd , buf , ret);
	}
	
	close(s_fd);
    close(c_fd);
	return 0;
}

2.客户端程序的编写

(1)socket。

(2)connect。

概念:端口号,实质就是一个数字编号,用来在一台主机的操作系统中唯一地标识一个能上网的进程。网络上传输的每一个数据包都包含了发送方和接收方的IP地址和端口号。

myClient.c

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

#define SER_PORT		8888				   // 服务器开放给我们的IP地址和端口号
#define SER_ADDR_IP		"192.168.0.100"		

int main(void)
{
	int s_fd = 0 ,  ret = 0;		   //文件描述符
	struct sockaddr_in ser_sockaddr = {0};     //服务器 sockaddr数据结构
	char buf[1024] = {0};	
	
    //第1步: 创建socket , 得到监听fd
	s_fd = socket(AF_INET , SOCK_STREAM , 0);
	if(-1 == s_fd)
	{
		perror("socket");
		return -1;
	}
	printf("socket = %d\n" , s_fd);
	
	// 第2步:connect连接服务器
	ser_sockaddr.sin_family = AF_INET;					// 设置地址族为IPv4
	ser_sockaddr.sin_port = htons(SER_PORT);			// 设置地址的端口号信息:本地--》网络 (IP)
	ser_sockaddr.sin_addr.s_addr = inet_addr(SER_ADDR_IP);	// 设置IP地址
	ret = connect(s_fd, (const struct sockaddr *)&ser_sockaddr, sizeof(ser_sockaddr));
	if (ret < 0)
	{
		perror("listen");
		return -1;
	}
	printf("connect successfully.\n");
	
	//小写 --> 大写 or 大写 --> 小写
	while(1)
	{
		memset(buf , 0 , sizeof(buf));
		gets(buf);
		write(s_fd , buf , sizeof(buf));
		ret = read(s_fd , buf , sizeof(buf));  //阻塞读取
		if(ret == 0) break;  				   //客户端断开连接
		
		write(STDOUT_FILENO , buf , ret);
		printf("\n");
	}
	
	close(s_fd);
	return 0;
}

3.运行效果:

7.出错处理封装函数

        上面的例子不仅功能简单,而且简单到几乎没有什么错误处理,我们知道,系统调用不能保证每次都成功,必须进行出错处理,这样一方面可以保证程序逻辑正常,另一方面可以迅速得到故障信息。

        为使错误处理的代码不影响主程序的可读性,我们把与socket相关的一些系统函数加上错误处理代码包装成新的函数,做成一个模块wrap.c:

wrap.c

#include "wrap.h"

void perr_exit(const char *s)
{
	perror(s);
	exit(-1);
}

int Socket(int domain, int type, int protocol)
{
	int n;

	if ((n = socket(domain, type, protocol)) < 0)
		perr_exit("socket error");

	return n;
}

int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

	if ((n = bind(fd, sa, salen)) < 0)
		perr_exit("bind error");

    return n;
}

int Listen(int fd, int backlog)
{
    int n;

	if ((n = listen(fd, backlog)) < 0)
		perr_exit("listen error");

    return n;
}


int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
	int n;

again:
	if ((n = accept(fd, sa, salenptr)) < 0) {
		if ((errno == ECONNABORTED) || (errno == EINTR))	//被信号打断
			goto again;
		else
			perr_exit("accept error");
	}
	return n;
}


int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;
    n = connect(fd, sa, salen);
	if (n < 0) {
		perr_exit("connect error");
    }

    return n;
}


ssize_t Read(int fd, void *buf, size_t count)
{
	ssize_t n;

again:
	if ( (n = read(fd, buf, count)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}

	return n;
}

ssize_t Write(int fd, const void *buf, size_t count)
{
	ssize_t n;

again:
	if ((n = write(fd, buf, count)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}

int Close(int fd)
{
    int n;
	if ((n = close(fd)) == -1)
		perr_exit("close error");

    return n;
}

/*参三: 应该读取的字节数*/                          //socket 4096  readn(cfd, buf, 4096)   nleft = 4096-1500
ssize_t Readn(int fd, void *vptr, size_t n)
{
	size_t  nleft;              //usigned int 剩余未读取的字节数
	ssize_t nread;              //int 实际读到的字节数
	char   *ptr;

	ptr = vptr;
	nleft = n;                  //n 未读取字节数 , 需要读取的数据量

	while (nleft > 0) 
	{
		if ((nread = read(fd, ptr, nleft)) < 0)  //阻塞等待数据
		{
			if (errno == EINTR)		//被信号杀死
				nread = 0;
			else					//读出错
				return -1;
		} else if (nread == 0)	//没有数据了
			return 0;
			
		nleft -= nread;   //nleft = nleft - nread 
		ptr += nread;     //更新缓冲区指针
	}
	return n - nleft;  //返回没读够的字节数
}

ssize_t Writen(int fd, const void *vptr, size_t n)
{
	size_t nleft;
	ssize_t nwritten;
	const char *ptr;

	ptr = vptr;
	nleft = n;
	while (nleft > 0) 
	{
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) 
		{
			if (nwritten < 0 && errno == EINTR)
				nwritten = 0;
			else
				return -1;
		}
		nleft -= nwritten;
		ptr += nwritten;
	}
	return n;
}

static ssize_t my_read(int fd, char *ptr)
{
	static int read_cnt;
	static char *read_ptr;
	static char read_buf[100];

	if (read_cnt <= 0) {
again:
		if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) 
		{   //"hello\n"
			if (errno == EINTR)
				goto again;
			return -1;
		} else if (read_cnt == 0)
			return 0;

		read_ptr = read_buf;
	}
	read_cnt--;
	*ptr = *read_ptr++;

	return 1;
}

/*readline --- fgets*/    
//传出参数 vptr
ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
	ssize_t n, rc;
	char    c, *ptr;
	ptr = vptr;

	for (n = 1; n < maxlen; n++) 
	{
		if ((rc = my_read(fd, &c)) == 1) 
		{   //ptr[] = hello\n
			*ptr++ = c;
			if (c == '\n')
				break;
		} 
		else if (rc == 0) 
		{
			*ptr = 0;
			return n-1;
		} else
			return -1;
	}
	*ptr = 0;

	return n;
}

wrap.h

#ifndef __WRAP_H_
#define __WRAP_H_

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

void perr_exit(const char *s);
int Socket(int domain, int type, int protocol);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
ssize_t Read(int fd, void *buf, size_t count);
ssize_t Write(int fd, const void *buf, size_t count);
int Close(int fd);

ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t Readline(int fd, void *vptr, size_t maxlen);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值