欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

socket编程——TCP协议(C语言代码实现)

程序员文章站 2022-04-08 21:39:22
...

Linux系统下socket编程 ——TCP协议

 一、相关API

      1、创建套接字                 
                #include <sys/socket.h>
                int socket(int domain, int type, int protocol);
                    返回值:成功返回值套接字的文件描述符  失败 -1
                    参数:domain -->地址协议类型,常用的有:
                            AF_INET或者PF_INET    -->ipv4地址
                            AF_INET6或者PF_INET6  -->ipv6地址
                            AF_UNIX或者PF_UNIX或者AF_LOCAL  -->unix本地域套接字
                         type -->你创建的套接字类型
                            tcp套接字(流式套接字/数据流套接字) --> SOCK_STREAM
                            udp套接字(数据报套接字) --> SOCK_DGRAM         
                         protocol -->扩展协议,默认设置为0
       2、绑定ip和端口号
                #include <sys/types.h>        
                #include <sys/socket.h>
                int bind(int socket, const struct sockaddr *address,socklen_t address_len);
                    返回值:成功 0  失败 -1
                    参数:socket-->套接字
                        struct sockaddr--> 通用地址结构体,兼容ipv4和ipv6
                        {
                           sa_family_t sa_family;-->存放地址协议类型
                           char        sa_data[14];-->存放ip和端口号
                        }
                        struct sockaddr_in --> ipv4地址结构体
                        {
                            unsigned short         sin_family; -->存放地址协议类型AF_INET或者AF_INET6
                            unsigned short int     sin_port; -->端口号
                            struct in_addr         sin_addr; -->绑定自己的ip地址
                                struct in_addr
                                {
                                    in_addr_t s_addr;
                                }
                            unsigned char          sin_zero; -->充数的,打酱油的,为了跟通用地址结构体大小保持一致
                        }
                        struct sockaddr_in6 -->ipv6地址结构体
                        {
                            sa_family_t     sin6_family;    // 地址族 
                            u_int16_t       sin6_port;      // 端口号 
                            struct in6_addr sin6_addr;      // IPv6地址结构体
                            struct in6_addr 
                            {
                                unsigned char   sa_addr[16];    // IPv6地址 
                            };
                            u_int32_t       sin6_flowinfo;  // 流信息 
                            u_int32_t       sin6_scope_id;  // scope ID 
                        }; 
                        address_len -->地址结构体的大小
       3、ip和端口号的转换
                大小端:
                    大端序:数据的高字节存放在低地址,低字节存放在高地址
                    小端序:数据的高字节存放在高地址,低字节存放在低地址
                    ubuntu系统:小端序存放,称之为主机字节序
                    网络上的数据:大端序存放,称之为网络字节序
                将主机字节序转换成网络字节序
                    转换ip:
                        #include <sys/socket.h>
                        #include <netinet/in.h>
                        #include <arpa/inet.h>
                        in_addr_t inet_addr(const char *cp);
                            返回值:大端序ip   失败 -1
                            参数:cp -->小端序ip,点分十进制格式的ip
                        int inet_aton(const char *cp, struct in_addr *inp);
                    转换端口号:
                        uint32_t htonl(uint32_t hostlong);//某些特定情况下转换ip
                        uint16_t htons(uint16_t hostshort);
                        uint32_t ntohl(uint32_t netlong);                               
                        规律:h  -->host
                             n  -->network
                             l  -->long 跟ip转换有关
                             s  -->short 跟端口转换有关                        
                将网络字节序转换成主机字节序
                    转换ip:
                        char *inet_ntoa(struct in_addr in);
                        const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
                            参数:af -->地址协议 AF_INET 
                                 src -->存放你要转换的网络字节序ip
                                 dst -->存放转换好的主机字节序ip
                                 size -->ip地质大小
                    转换端口号:
                        uint16_t ntohs(uint16_t netshort);         
        4、连接
                #include <sys/types.h>   
                #include <sys/socket.h>
                int connect(int socket, const struct sockaddr *address, socklen_t address_len); 
                参数:address -->存放对方的ip和端口号         
                     address_len -->地址结构体的大小         
       5、监听(服务器端)
                int listen(int socket, int backlog);
                    参数:backlog -->同时能够接受的最大客户端连接数量
                        listen(tcpsock,7);                 
       6、接受连接请求(服务器端)
                int accept(int socket, struct sockaddr *restrict address,socklen_t *restrict address_len);
                    返回值:成功 新的套接字用于等一会收发信息 失败 -1(要产生新的套接字,主要是为了区分不同客户端的连接)                            
                    参数:address -->存放连接服务器的那个客户端的ip和端口
                         address_len -->地址结构体的大小
                    特点:在没有客户端连接服务器的时候,服务器阻塞在accept(主要是阻塞在socket上)
                         当有客户端成功连接服务器的时候,会解除accept阻塞,产生新套接字
            7、收发信息
                ssize_t send(int socket, const void *buffer, size_t length, int flags);
                    返回值:length是多少,就返回多少
                    参数:flags -->默认设置0
                         length-->发送字节数
                ssize_t recv(int socket, void *buffer, size_t length, int flags);            返回值:跟send有关
                    参数:flags -->默认设置0
                               -->MSG_WAITALL -->recv会一直等待length个字节的数据全部接收完毕才退出 
                    三种情况
                        情况一: 成功 大于0  
                        情况二: 等于0,表示客户端或者服务器断开连接了
                        情况三: -1 失败
                    特点:正常情况下,收不到消息阻塞,但是如果客户端或者服务器断开连接,不阻塞,然后返回0     
            8、遇到的问题
                问题一: 绑定失败!: Address already in use
                原因:你退出程序的时候,使用的端口号并不是立马就释放掉了,会延时一段时间(不同系统,时间不一样),导致你再次运行程序,提示绑定失败
                解决方案:
                        第一种:更换新的端口号,通过主函数传参更换
                        第二种:取消端口号绑定限制(设置套接字的属性)                               
                        int  setsockopt(int socket,int level,int option_name,void *option_value,socklen_t option_len);
                            参数:level       -->SOL_SOCKET
                                 option_name -->SO_REUSEADDR  //取消端口号绑定限制
                                             -->SO_BROADCAST  //设置udp广播
                                 option_value
                                 option_len
                               int on=1;
                        例如: setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
                问题二: 服务器突然断开,导致客户端疯狂打印,不阻塞了
                原因:recv正常情况下,收不到消息阻塞,但是如果客户端或者服务器断开连接,不阻塞,然后返回0

 

二、代码实现

 1、客户端

        //单向一次性通信
#include <sys/types.h>   
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{
	int tcpsock;
	char buf[50]={0};
	int ret;
	//定义ipv4地址结构体变量
	struct sockaddr_in bindaddr;
	bzero(&bindaddr,sizeof(bindaddr));
	bindaddr.sin_family=AF_INET;
	bindaddr.sin_port=htons(10000);//自己指定了一个端口
	bindaddr.sin_addr.s_addr=inet_addr("192.168.22.131");//绑定自己的ip地址,将小端序ip转换成大端序
    //方法二 inet_aton("192.168.22.9",&(bindaddr.sin_addr));

	//定义ipv4地址结构体变量存放你要连接的服务器的ip和端口
	struct sockaddr_in serveraddr;
	bzero(&serveraddr,sizeof(serveraddr));
	serveraddr.sin_family=AF_INET;
	serveraddr.sin_port=htons(20000);//服务器的端口号
	serveraddr.sin_addr.s_addr=inet_addr("192.168.22.22");//服务器的ip

	//创建tcp类型的套接字
	tcpsock=socket(AF_INET,SOCK_STREAM,0);
	if(tcpsock==-1)//if(-1==tcpsock)
	{
		perror("创建套接字!\n");
		return -1;
	}
	//绑定ip和端口号
	ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
	if(ret==-1)
	{
		perror("绑定失败!\n");
		return -1;
	}
	//连接服务器
	ret=connect(tcpsock,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
	if(ret==-1)
	{
		perror("连接服务器失败!\n");
		return -1;
	}
	//聊天
	printf("请输入要发送给服务器的信息!\n");
	scanf("%s",buf);
	ret=send(tcpsock,buf,50,0);
	printf("send的返回值是:%d\n",ret);
	//挂机
	close(tcpsock);
	return 0;
}

2、服务器端

pthread_t id; //线程ID号
int newsock;//连上新的客户端产生的新套接字

void *recv1(void *arg) //开一个线程来回复信息
{
	int ret;
	char buf[50]={0};
	while(1)
	{	
		printf("请输入要发送给客户端的信息!\n");
		scanf("%s",buf);
		ret=send(newsock,buf,50,0);
		if(strcmp(buf,"quit")==0)
		exit(-1);
	}
}

int main()
{
	char buf[50]={0};
	char ipbuf[20]={0};
	int tcpsock;
	int ret;
	//定义ipv4地址结构体变量
	struct sockaddr_in bindaddr;
	bzero(&bindaddr,sizeof(bindaddr));
	bindaddr.sin_family=AF_INET;
	bindaddr.sin_port=htons(20000);//自己指定了一个端口
	bindaddr.sin_addr.s_addr=inet_addr("192.168.22.124");//绑定自己的ip地址,将小端序ip转换成大端序
	
	//定义ipv4地址结构体变量存放连接成功的那个客户端的ip和端口号
	struct sockaddr_in clientaddr;
	bzero(&clientaddr,sizeof(clientaddr));
	int addrsize=sizeof(clientaddr);
	//创建tcp类型的套接字
	tcpsock=socket(AF_INET,SOCK_STREAM,0);
		if(tcpsock==-1)//if(-1==tcpsock)
		{
			perror("创建套接字!\n");
			return -1;
		}

	//绑定ip和端口号
	ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
		
	//监听
	ret=listen(tcpsock,7);
		if(ret==-1)
		{
			perror("监听失败!\n");
			return -1;
		}

	pthread_create(&id,NULL,recv1,NULL); //创建子线程进行收信息
	//接收信息
	while(1)
	{	
		newsock=accept(tcpsock,(struct sockaddr *)&clientaddr,&addrsize);	//接受连接请求
		if(newsock==-1)
		{
			perror("接受连接请求失败!\n");
			return -1;
		}
		//打印当前连接成功的那个客户端的ip跟端口号
	    printf("目前连接成功的客户端ip是:%s 端口号是:%hu\n",inet_ntop(AF_INET,&(clientaddr.sin_addr),ipbuf,20),ntohs(clientaddr.sin_port));
		ret=recv(newsock,buf,50,0);
		printf("收到的信息是:%s recv返回值是:%d\n",buf,ret);
		if(strcmp(buf,"quit")==0)
			break;
	}
	//recv(tcpsock,buf,50,0);
	//挂机
	close(tcpsock);
	close(newsock);
	return 0;
}

3、TCP广播和点播(使用多线程和单链表) 

 

相关标签: socket编程