Linux下socket简单通信

服务器端:

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

#define MAXLINE 4096

/**************程序流程*******************
1 用socket()函数创建一个socket
2 用bind()绑定到一个本地的地址,这样其他的socket可以用connect()连接上去
3 用listen()指出愿意接收连接并指定进来的连接的队列限制
4 用accept()函数来接收连接
*****************************************/

int main(int argc, char const *argv[])
{
    int listenfd; //监听套接字描述符,管理客户端连接到服务端的个数
    int connfd;   //连接套接字描述符,被send()和recv()函数所使用
    struct sockaddr_in servaddr; //套接字地址结构体
    //结构体具体定义见sockaddr_in.jpg
    char buff[MAXLINE];
    int n;
    if((listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1){ 
        //socket函数向系统申请一个通信端口 原型:int socket(int domain, int type, int protocol);
        //domain@ PF_INET, AF_INET: Ipv4网络协议; PF_INET6, AF_INET6: Ipv6网络协议
        //type@ SOCK_STREAM: 提供面向连接的稳定数据传输,即TCP协议。SOCK_DGRAM: 使用不连续不可靠的数据包连接。
        //protocal@ 传输协议编号,一般设置为0就好。

        printf("create socket error: %s(errno:%d)\n",strerror(errno),errno);
        //errno 为全局变量,包含在<error.h>中,程序出错时自动填充。错误号的源代码定义:https://www.cnblogs.com/xrcun/p/3210889.html
        //strerror()函数不是线程安全的,strerror_r()是线程安全的。

        return 0;
    }
    memset(&servaddr,0,sizeof(servaddr)); //初始化结构体
    servaddr.sin_family = AF_INET;   //设置地址家族,此处为IPv4

    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 
        //设置地址:servaddr.sin_addr.s_addr是ip地址,
        //htonl()的作用:将主机的unsigned long值转换成网络字节顺序(32位);作用相反的函数即把网络字节顺序转化成主机序列为ntohl()函数;htons()就是转化为short类型的(16位两字节)
        //机器上可能有多块网卡,也就有多个IP地址,这时候你要选择绑定在哪个IP上面,如果指定为INADDR_ANY,那么系统将绑定默认的网卡【即IP地址】
        //servaddr.sin_addr.s_addr = inet_addr("192.168.81.130")手动设置服务器的IP地址;inet_addr()作用是将一个IP字符串转化为一个网络字节序的整数值,inet_ntoa()作用与其相反
    servaddr.sin_port = htons(6666);  //设置端口

    if(bind(listenfd,(struct sockaddr*) &servaddr , sizeof(servaddr)) == -1){
        //sockaddr的缺陷是:把目标地址和端口信息混在一起了; sockaddr_in解决了sockaddr的缺陷,但是参数传递时需要类型转换为sockaddr。
        printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
        return 0;
    }

    if(listen(listenfd,10) == -1){
        //监听端口后,被动套接字会等待连接,此时系统维护着两个队列,第一个队列存放的是已建立连接的套接字(即完成三次握手后的),第二个队列存放的是未建立连接的套接字(处在三次握手中的)。每次accept函数会将第一个队列中的套接字返回,进行通信,以完成网络传输。
        //所以listen的第二个参数意味着这两个队列的总和大小
        printf("listen socket error:%s(errno: %d)\n",strerror(errno),errno);
        return 0;
    }

    printf("========== waiting for client's request =============\n");

	connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);

    while(1){
        if( connfd == -1){
            //等待客户端连接,连接将加入到等待接受(accept())的队列中
            //第一个参数为本地(服务端监听描述符),第二个参数存有客户端IP地址和端口号,第三个参数为sockaddr*指向区域的长度
            //accept在有客户端连接上来时会被阻塞
            printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
            continue;
        }
        n = recv(connfd,buff,MAXLINE,0);
        //buff:缓冲区名字 MAXLINE:缓冲区大小 最后一个参数(flags):
        //通常flags设置为0,此时recv()函数读取tcp 缓冲区中的数据到buf中,并从tcp 缓冲区中移除已读取的数据,
        //如果把flags设置为MSG_PEEK,仅仅是把tcp 缓冲区中的数据读取到buf中,没有把已读取的数据从tcp 缓冲区中移除,如果再次调用recv()函数仍然可以读到刚才读到的数据。

        buff[n] = '\0'; 
        //scanf之类的字符串处理函数会自动将缓冲区的最后一字节设置为 '\0',但是诸如read()等函数就不会自动设置
        //TCP缓冲区大小一般分为三级,详见 https://blog.csdn.net/nawenqiang/article/details/81503870

        printf("recv msg from client: %s\n",buff);
		memset(buff,0,sizeof(buff)/sizeof(char) ); //发送完一次后清空缓冲区,为下一次发送做好准备。
    }
	close(connfd);
    close(listenfd);

    return 0;
}

客户端:

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

#define MAXLINE 4096

int main(int argc, char** argv) {
	int   sockfd, n;
	char recvLine[MAXLINE], sendLine[MAXLINE];
	struct sockaddr_in servaddr;

	if (argc != 2) {   //命令行参数为要连接的服务端IP地址
		printf("Usage: ./Client <ip_address>\n");
		return 0;
	}

	if ( (sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0 ) {
		printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
		return 0;
	}

	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(6666);

	if ( inet_pton(AF_INET,argv[1],&servaddr.sin_addr) <= 0 ) {
		printf("inet_pton error for %s\n", argv[1]);
		return 0;
	}

	if (connect(sockfd,(struct sockaddr*) & servaddr,sizeof(servaddr)) < 0 ) {
		//客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。connect()成功返回0,出错返回-1
		printf("connect error: %s(errno: %d)\n", strerror(errno), errno);
		return 0;
	}

	printf("send msg to server: \n");
	while(1){
		fgets(sendLine,sizeof(sendLine),stdin);

		if ( send(sockfd,sendLine,sizeof(sendLine),0) < 0 ) {
			printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
			return 0;
		}
		if (strcmp(sendLine, "exit\n") == 0)
			break;
		memset(sendLine, 0, sizeof(sendLine));
	}
	
	close(sockfd);
	return 0;
}

makefile文件:

Server: Server.o
Server.o: Server.cpp
	gcc -c Server.cpp -o Server.o
.PHONY:clean
clean:
	-rm -rf *.o

gcc  和 g++的区别:

后缀为.c的,gcc把它当成c程序;g++当作c++程序,即跟.cpp没有区别。

后缀.cpp的,gcc与g++都当成c++程序。

gcc不能自动链接c++库,g++会自动链接c++库。

c++程序中,预编译后会存在extern “C”标识符,即是用gcc按照c程序规则编译,这是兼容C程序的表现。

gcc也可以编译c++程序。

gcc编译.c文件,.c文件中一定不能出现c++语法和库操作。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值