linux socket编程TCP/UDP

1.0 地址    通用地址(sockaddr),  IPV4地址sockaddr_in           2.0  端口         3.0大端模式(网络应用)   小端模式

网络字节序  <--->本机字节序     htons(),ntohs(),htonl()和ntohl().

网络字节序 <---->IP字符串    

  •  int inet_aton(const char     *strptr    ,    struct in_addr   *addrptr);
  • in_addr_t   inet_addr(const char     *strptr);
  • char *inet_ntoa (struct in_addr      inaddr);

struct in_addr {

    in_addr_t s_addr;

};

in_addr_t一般为32位的unsigned int,其字节顺序为网络字节序,即该无符号数采用大端字节序。其中每8位表示一个IP地址中的一个数值。

socket族   AF_INET       

1.1. 使用TCP协议的流程图

TCP通信的基本步骤如下:

服务端:socket---bind---listen---while(1){---accept---recv---send---close---}---close

客户端:socket----------------------------------connect---send---recv-----------------close

1.2. 使用UDP协议的流程图

UDP通信流程图如下:

服务端:socket---bind---recvfrom---sendto---close

客户端:socket----------sendto---recvfrom---close

 服务器端

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

#if 0

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

int socket(int domain, int type, int protocol);
// 创建用于通信的端口,返回描述符 

// domain 包含以下域:
// Name                Purpose                          Man page
// AF_UNIX, AF_LOCAL   Local communication              unix(7)
// AF_INET             IPv4 Internet protocols          ip(7)
// AF_INET6            IPv6 Internet protocols          ipv6(7)
// AF_IPX              IPX - Novell protocols
// AF_NETLINK          Kernel user interface device     netlink(7)
// AF_X25              ITU-T X.25 / ISO-8208 protocol   x25(7)
// AF_AX25             Amateur radio AX.25 protocol
// AF_ATMPVC           Access to raw ATM PVCs
// AF_APPLETALK        AppleTalk                        ddp(7)
// AF_PACKET           Low level packet interface       packet(7)
// AF_ALG              Interface to kernel crypto API

// type 有以下选项:
// SOCK_STREAM 基于流的,适用于 TCP 套接字
// SOCK_DGRAM  基于数据报的,适用于 UDP 套接字
// SOCK_RAW    原始套接字

// protocol 子协议,如果没有子协议则为 0

// 成功返回套接字的描述符,失败返回 -1


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

int bind(int sockfd, const struct sockaddr *addr,
			socklen_t addrlen);
// 绑定长度为 addrlen 的 addr 地址到 sockfd 套接字
// sockfd  套接字描述符
// addr    通用地址指针,传递具体地址时要进行强制类型转换
// addrlen 地址长度
// 成功返回 0,失败返回 -1

// 通用地址结构体
struct sockaddr {
	sa_family_t sa_family;
	char        sa_data[14];
};


#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
// 只能用于 TCP
// 从 sockfd 套接字接收网络信息,保存到大小为 len 的缓冲区 buf 中
// flags 可取 0 或以下值按位或:
//     MSG_DONTWAIT  非阻塞
//     MSG_OOB       带外数据(紧急数据)
//     MSG_PEEK      只提取数据而不从接收队列中删除它
//     MSG_WAITALL   阻塞直到所有请求都满足
// recv() 等价于以下调用:
//     recvfrom(fd, buf, len, flags, NULL, 0));

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
			struct sockaddr *src_addr, socklen_t *addrlen);
// 用于 UDP,但也可以用于 TCP
// 从 sockfd 套接字接收网络信息,保存到大小为 len 的缓冲区 buf 中
// 保存发送者的地址信息到大小为 addrlen 的地址 src_addr 中
// 注意,尽管 addrlen 是用来存放发送者的地址的大小的,但在有的系统
// 中必须提供一个正确的大小,才能正确接收网络数据

// 以上函数成功时返回收到的字节数,失败时返回 -1
// 返回 0 的情况:
// 1. 基于流的套接字(TCP),发送方已关闭,接收方会收到 0 个字节,
//    这相当于"文件尾"
// 2. 基于数据报的套接字(UDP)允许长度为 0 的数据报,当收到这样的
//    数据报,返回 0 个字节
// 3. 从流套接字收到 0 个字节时返回 0 字节


#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// 只用于 TCP 
// 将数据大小为 len 的 buf 中的数据通过 sockfd 发送
// flags 取值为 0 或以参下参数按位或:
//     MSG_DONTWAIT  非阻塞
//     MSG_NOSIGNAL  向已关闭的流套接字写不产生 SIGPIPE 信号 
//     MSG_OOB       带外数据(紧急数据)

// send() 等价于以下调用:
//     sendto(sockfd, buf, len, flags, NULL, 0);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
			const struct sockaddr *dest_addr, socklen_t addrlen);
// 既可以用于 UDP 也可以用于 TCP
// 将数据大小为 len 的 buf 中的数据通过 sockfd 发送到大小为 addrlen
// 的地址 dest_addr
// send() 和 sendto() 返回成功发送的字节数,或失败返回 -1


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

int getsockopt(int sockfd, int level, int optname,
			void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
			const void *optval, socklen_t optlen);
// 获取/设置套接字选项
// level 可取以下值:
//     SOL_SOCKET     基本套接口  
//     IPPROTO_IP     IPv4套接口  
//     IPPROTO_IPV6   IPv6套接口  
//     IPPROTO_TCP    TCP套接口  
// optname 可取以下值:
//     SO_BROADCAST 广播
//     SO_RCVBUF    接收缓冲区
//     SO_REUSEADDR 地址可重用
//     SO_REUSEPORT 端口可重用
//     SO_SNDBUF    发送缓冲区
// 成功返回 0,失败返回 -1

#endif

int main(int argc, char **argv)
{
	int sockfd;
	struct sockaddr_in svr_addr, clt_addr;  //使用IPV4地址,但recvfrom与sendto 都是用通用地址
	in_port_t port;
	in_addr_t inaddr;
	char *ip = "192.168.177.151";
	int optval = 1;  // 非 0 使能地址可重用
	socklen_t socklen = sizeof clt_addr, optlen = sizeof optval;
	char recv_buf[32] = "";
	char send_buf[32] = "Hello client";

	// 检查参数个数
	if (argc != 3)
	{
		printf("用法:%s <本机 IP> <端口号>\n", argv[0]);
		return -1;
	}
	ip = argv[1];
	port = atoi(argv[2]);

	// 创建 UDP 套接字
	sockfd = socket(AF_INET /*PF_INET*/, SOCK_DGRAM, 0);
	if (-1 == sockfd)
	{
		perror("创建套接字失败");
		return -1;
	}

	// 设置套接字选项 - 地址可重用   //防止别的进程地址没有释放
	if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, 
					&optval, optlen))
	{
		perror("设置地址可重用失败");
		goto _out;
	}

	// 绑定本机地址
	inaddr = inet_addr(ip);
	if (INADDR_NONE == inaddr)
	{
		printf("%s 是非法 IP 地址\n", ip);
		goto _out;
	}
	svr_addr.sin_family = AF_INET;   // PF_INET
	svr_addr.sin_port = htons(port); // 端口必须转换为网络字节序
	//svr_addr.sin_addr.s_addr = inaddr;   // 绑定到本机的某一个 IP
	// 绑定到本机任意网络接口    可能本机有多个IP
	svr_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
	if (-1 == bind(sockfd, (struct sockaddr *) &svr_addr, sizeof svr_addr))
	{
		perror("绑定失败");
		goto _out;
	}

	// 收
	if (-1 == recvfrom(sockfd, recv_buf, sizeof recv_buf, 0, 
					(struct sockaddr *) &clt_addr, &socklen))
	{
		perror("接收失败");
		goto _out;
	}
	printf("收到 %s(%d) 发来的消息:%s\n", 
				inet_ntoa(clt_addr.sin_addr), 
				ntohs(clt_addr.sin_port), recv_buf);

	// 发
	if (-1 == sendto(sockfd, send_buf, strlen(send_buf), 0, 
					(struct sockaddr *) &clt_addr, socklen))
	{
		perror("发送失败");
		goto _out;
	}
	printf("发送成功\n");

_out:
	// 关闭套接字
	close(sockfd);

	return 0;
}

 客户端

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

#if 0

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

int socket(int domain, int type, int protocol);
// 创建用于通信的端口,返回描述符 

// domain 包含以下域:
// Name                Purpose                          Man page
// AF_UNIX, AF_LOCAL   Local communication              unix(7)
// AF_INET             IPv4 Internet protocols          ip(7)
// AF_INET6            IPv6 Internet protocols          ipv6(7)
// AF_IPX              IPX - Novell protocols
// AF_NETLINK          Kernel user interface device     netlink(7)
// AF_X25              ITU-T X.25 / ISO-8208 protocol   x25(7)
// AF_AX25             Amateur radio AX.25 protocol
// AF_ATMPVC           Access to raw ATM PVCs
// AF_APPLETALK        AppleTalk                        ddp(7)
// AF_PACKET           Low level packet interface       packet(7)
// AF_ALG              Interface to kernel crypto API

// type 有以下选项:
// SOCK_STREAM 基于流的,适用于 TCP 套接字
// SOCK_DGRAM  基于数据报的,适用于 UDP 套接字
// SOCK_RAW    原始套接字

// protocol 子协议,如果没有子协议则为 0

// 成功返回套接字的描述符,失败返回 -1


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

int bind(int sockfd, const struct sockaddr *addr,
			socklen_t addrlen);
// 绑定长度为 addrlen 的 addr 地址到 sockfd 套接字
// sockfd  套接字描述符
// addr    通用地址指针,传递具体地址时要进行强制类型转换
// addrlen 地址长度
// 成功返回 0,失败返回 -1

// 通用地址结构体
struct sockaddr {
	sa_family_t sa_family;
	char        sa_data[14];
};


#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
// 只能用于 TCP
// 从 sockfd 套接字接收网络信息,保存到大小为 len 的缓冲区 buf 中
// flags 可取 0 或以下值按位或:
//     MSG_DONTWAIT  非阻塞
//     MSG_OOB       带外数据(紧急数据)
//     MSG_PEEK      只提取数据而不从接收队列中删除它
//     MSG_WAITALL   阻塞直到所有请求都满足
// recv() 等价于以下调用:
//     recvfrom(fd, buf, len, flags, NULL, 0));

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
			struct sockaddr *src_addr, socklen_t *addrlen);
// 用于 UDP,但也可以用于 TCP
// 从 sockfd 套接字接收网络信息,保存到大小为 len 的缓冲区 buf 中
// 保存发送者的地址信息到大小为 addrlen 的地址 src_addr 中
// 注意,尽管 addrlen 是用来存放发送者的地址的大小的,但在有的系统
// 中必须提供一个正确的大小,才能正确接收网络数据

// 以上函数成功时返回收到的字节数,失败时返回 -1
// 返回 0 的情况:
// 1. 基于流的套接字(TCP),发送方已关闭,接收方会收到 0 个字节,
//    这相当于"文件尾"
// 2. 基于数据报的套接字(UDP)允许长度为 0 的数据报,当收到这样的
//    数据报,返回 0 个字节
// 3. 从流套接字收到 0 个字节时返回 0 字节


#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// 只用于 TCP 
// 将数据大小为 len 的 buf 中的数据通过 sockfd 发送
// flags 取值为 0 或以参下参数按位或:
//     MSG_DONTWAIT  非阻塞
//     MSG_NOSIGNAL  向已关闭的流套接字写不产生 SIGPIPE 信号 
//     MSG_OOB       带外数据(紧急数据)

// send() 等价于以下调用:
//     sendto(sockfd, buf, len, flags, NULL, 0);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
			const struct sockaddr *dest_addr, socklen_t addrlen);
// 既可以用于 UDP 也可以用于 TCP
// 将数据大小为 len 的 buf 中的数据通过 sockfd 发送到大小为 addrlen
// 的地址 dest_addr
// send() 和 sendto() 返回成功发送的字节数,或失败返回 -1

#endif

int main(int argc, char **argv)
{
	int sockfd;
	struct sockaddr_in svr_addr, clt_addr;
	in_port_t port;
	in_addr_t inaddr;
	socklen_t socklen = sizeof svr_addr;
	char *ip;
	char recv_buf[32] = "";
	char send_buf[32] = "Hello server";

	// 检查参数个数
	if (argc != 3)
	{
		printf("用法:%s <服务器 IP> <服务器端口号>\n", argv[0]);
		return -1;
	}
	ip = argv[1];
	port = atoi(argv[2]);

	// 创建 UDP 套接字
	sockfd = socket(AF_INET /*PF_INET*/, SOCK_DGRAM, 0);
	if (-1 == sockfd)
	{
		perror("创建套接字失败");
		return -1;
	}

	// 客户端不必绑定本机地址

	// 配置服务器地址
	inaddr = inet_addr(ip);
	if (INADDR_NONE == inaddr)
	{
		printf("%s 是非法 IP\n", ip);
		goto _out;
	}
	svr_addr.sin_family = AF_INET;
	svr_addr.sin_port = htons(port);
	svr_addr.sin_addr.s_addr = inaddr;
	
	// 发
	if (-1 == sendto(sockfd, send_buf, strlen(send_buf), 0, 
					(struct sockaddr *) &svr_addr, socklen))
	{
		perror("发送失败");
		goto _out;
	}
	printf("发送成功\n");

	// 收
	if (-1 == recvfrom(sockfd, recv_buf, sizeof recv_buf, 0, 
					(struct sockaddr *) &clt_addr, &socklen))
	{
		perror("接收失败");
		goto _out;
	}
	printf("收到 %s(%d) 发来的消息:%s\n", 
				inet_ntoa(clt_addr.sin_addr), 
				ntohs(clt_addr.sin_port), recv_buf);

_out:
	// 关闭套接字
	close(sockfd);

	return 0;
}

猜你喜欢

转载自blog.csdn.net/h490516509/article/details/85629859